diff --git a/Demo/ViewController.swift b/Demo/ViewController.swift index c5130f0..ea47b80 100644 --- a/Demo/ViewController.swift +++ b/Demo/ViewController.swift @@ -11,25 +11,25 @@ import DropDown class ViewController: UIViewController { - //MARK: - Properties - + // MARK: - Properties + @IBOutlet weak var chooseArticleButton: UIButton! @IBOutlet weak var amountButton: UIButton! @IBOutlet weak var chooseButton: UIButton! @IBOutlet weak var centeredDropDownButton: UIButton! @IBOutlet weak var rightBarButton: UIBarButtonItem! let textField = UITextField() - - //MARK: - DropDown's - + + // MARK: - DropDown's + let chooseArticleDropDown = DropDown() let amountDropDown = DropDown() let chooseDropDown = DropDown() let centeredDropDown = DropDown() let rightBarDropDown = DropDown() - + lazy var dropDowns: [DropDown] = { - return [ + [ self.chooseArticleDropDown, self.amountDropDown, self.chooseDropDown, @@ -37,74 +37,74 @@ class ViewController: UIViewController { self.rightBarDropDown ] }() - - //MARK: - Actions - + + // MARK: - Actions + @IBAction func chooseArticle(_ sender: AnyObject) { chooseArticleDropDown.show() } - + @IBAction func changeAmount(_ sender: AnyObject) { amountDropDown.show() } - + @IBAction func choose(_ sender: AnyObject) { chooseDropDown.show() } - + @IBAction func showCenteredDropDown(_ sender: AnyObject) { centeredDropDown.show() } - + @IBAction func showBarButtonDropDown(_ sender: AnyObject) { rightBarDropDown.show() } - + @IBAction func changeDIsmissMode(_ sender: UISegmentedControl) { switch sender.selectedSegmentIndex { case 0: dropDowns.forEach { $0.dismissMode = .automatic } case 1: dropDowns.forEach { $0.dismissMode = .onTap } - default: break; + default: break } } - + @IBAction func changeDirection(_ sender: UISegmentedControl) { switch sender.selectedSegmentIndex { case 0: dropDowns.forEach { $0.direction = .any } case 1: dropDowns.forEach { $0.direction = .bottom } case 2: dropDowns.forEach { $0.direction = .top } - default: break; + default: break } } - + @IBAction func changeUI(_ sender: UISegmentedControl) { switch sender.selectedSegmentIndex { case 0: setupDefaultDropDown() case 1: customizeDropDown(self) - default: break; + default: break } } - + @IBAction func showKeyboard(_ sender: AnyObject) { textField.becomeFirstResponder() } - + @IBAction func hideKeyboard(_ sender: AnyObject) { view.endEditing(false) } - + func setupDefaultDropDown() { DropDown.setupDefaultAppearance() - + dropDowns.forEach { $0.cellNib = UINib(nibName: "DropDownCell", bundle: Bundle(for: DropDownCell.self)) $0.customCellConfiguration = nil } } - + func customizeDropDown(_ sender: AnyObject) { let appearance = DropDown.appearance() - + appearance.cellHeight = 60 appearance.backgroundColor = UIColor(white: 1, alpha: 1) appearance.selectionBackgroundColor = UIColor(red: 0.6494, green: 0.8155, blue: 1.0, alpha: 0.2) @@ -116,39 +116,41 @@ class ViewController: UIViewController { appearance.animationduration = 0.25 appearance.textColor = .darkGray // appearance.textFont = UIFont(name: "Georgia", size: 14) + appearance.separatorInset = .zero + appearance.separatorColor = .black if #available(iOS 11.0, *) { appearance.setupMaskedCorners([.layerMaxXMaxYCorner, .layerMinXMaxYCorner]) } - + dropDowns.forEach { /*** FOR CUSTOM CELLS ***/ $0.cellNib = UINib(nibName: "MyCell", bundle: nil) - - $0.customCellConfiguration = { (index: Index, item: String, cell: DropDownCell) -> Void in + + $0.customCellConfiguration = { (index: Index, _: String, cell: DropDownCell) -> Void in guard let cell = cell as? MyCell else { return } - + // Setup your custom UI components cell.logoImageView.image = UIImage(named: "logo_\(index % 10)") } /*** ---------------- ***/ } } - - //MARK: - UIViewController - + + // MARK: - UIViewController + override func viewDidLoad() { super.viewDidLoad() - + setupDropDowns() dropDowns.forEach { $0.dismissMode = .onTap } dropDowns.forEach { $0.direction = .any } - + view.addSubview(textField) } - //MARK: - Setup - + // MARK: - Setup + func setupDropDowns() { setupChooseArticleDropDown() setupAmountDropDown() @@ -156,18 +158,18 @@ class ViewController: UIViewController { setupCenteredDropDown() setupRightBarDropDown() } - + func setupChooseArticleDropDown() { chooseArticleDropDown.anchorView = chooseArticleButton - + // Will set a custom with instead of anchor view width // dropDown.width = 100 - + // By default, the dropdown will have its origin on the top left corner of its anchor view // So it will come over the anchor view and hide it completely // If you want to have the dropdown underneath your anchor view, you can do this: chooseArticleDropDown.bottomOffset = CGPoint(x: 0, y: chooseArticleButton.bounds.height) - + // You can also use localizationKeysDataSource instead. Check the docs. chooseArticleDropDown.dataSource = [ "iPhone SE | Black | 64G", @@ -176,38 +178,38 @@ class ViewController: UIViewController { "Asus Zenfone Max 4G", "Apple Watwh | Sport Edition" ] - + // Action triggered on selection - chooseArticleDropDown.selectionAction = { [weak self] (index, item) in + chooseArticleDropDown.selectionAction = { [weak self] (_, item) in self?.chooseArticleButton.setTitle(item, for: .normal) } - - chooseArticleDropDown.multiSelectionAction = { [weak self] (indices, items) in + + chooseArticleDropDown.multiSelectionAction = { [weak self] (_, items) in print("Muti selection action called with: \(items)") if items.isEmpty { self?.chooseArticleButton.setTitle("", for: .normal) } } - + // Action triggered on dropdown cancelation (hide) // dropDown.cancelAction = { [unowned self] in // // You could for example deselect the selected item // self.dropDown.deselectRowAtIndexPath(self.dropDown.indexForSelectedRow) // self.actionButton.setTitle("Canceled", forState: .Normal) // } - + // You can manually select a row if needed // dropDown.selectRowAtIndex(3) } - + func setupAmountDropDown() { amountDropDown.anchorView = amountButton - + // By default, the dropdown will have its origin on the top left corner of its anchor view // So it will come over the anchor view and hide it completely // If you want to have the dropdown underneath your anchor view, you can do this: amountDropDown.bottomOffset = CGPoint(x: 0, y: amountButton.bounds.height) - + // You can also use localizationKeysDataSource instead. Check the docs. amountDropDown.dataSource = [ "10 €", @@ -223,38 +225,38 @@ class ViewController: UIViewController { "110 €", "120 €" ] - + // Action triggered on selection - amountDropDown.selectionAction = { [weak self] (index, item) in + amountDropDown.selectionAction = { [weak self] (_, item) in self?.amountButton.setTitle(item, for: .normal) } } - + func setupChooseDropDown() { chooseDropDown.anchorView = chooseButton - + // By default, the dropdown will have its origin on the top left corner of its anchor view // So it will come over the anchor view and hide it completely // If you want to have the dropdown underneath your anchor view, you can do this: chooseDropDown.bottomOffset = CGPoint(x: 0, y: chooseButton.bounds.height) - + // You can also use localizationKeysDataSource instead. Check the docs. chooseDropDown.dataSource = [ "Lorem ipsum dolor", "sit amet consectetur", "cadipisci en..." ] - + // Action triggered on selection - chooseDropDown.selectionAction = { [weak self] (index, item) in + chooseDropDown.selectionAction = { [weak self] (_, item) in self?.chooseButton.setTitle(item, for: .normal) } } - + func setupCenteredDropDown() { // Not setting the anchor view makes the drop down centered on screen // centeredDropDown.anchorView = centeredDropDownButton - + // You can also use localizationKeysDataSource instead. Check the docs. centeredDropDown.dataSource = [ "The drop down", @@ -263,15 +265,15 @@ class ViewController: UIViewController { "it has no anchor view defined.", "Click anywhere to dismiss." ] - - centeredDropDown.selectionAction = { [weak self] (index, item) in + + centeredDropDown.selectionAction = { [weak self] (_, item) in self?.centeredDropDownButton.setTitle(item, for: .normal) } } - + func setupRightBarDropDown() { rightBarDropDown.anchorView = rightBarButton - + // You can also use localizationKeysDataSource instead. Check the docs. rightBarDropDown.dataSource = [ "Menu 1", diff --git a/DropDown/helpers/DPDConstants.swift b/DropDown/helpers/DPDConstants.swift index 52047be..6e5f55e 100644 --- a/DropDown/helpers/DPDConstants.swift +++ b/DropDown/helpers/DPDConstants.swift @@ -12,50 +12,46 @@ import UIKit internal struct DPDConstant { - internal struct KeyPath { + internal struct KeyPath { - static let Frame = "frame" + static let Frame = "frame" + } - } + internal struct ReusableIdentifier { - internal struct ReusableIdentifier { + static let DropDownCell = "DropDownCell" + } - static let DropDownCell = "DropDownCell" + internal struct UI { - } - - internal struct UI { - - static let TextColor = UIColor.black + static let TextColor = UIColor.black static let SelectedTextColor = UIColor.black - static let TextFont = UIFont.systemFont(ofSize: 15) - static let BackgroundColor = UIColor(white: 0.94, alpha: 1) - static let SelectionBackgroundColor = UIColor(white: 0.89, alpha: 1) - static let SeparatorColor = UIColor.clear - static let CornerRadius: CGFloat = 2 - static let RowHeight: CGFloat = 44 - static let HeightPadding: CGFloat = 20 - - struct Shadow { - - static let Color = UIColor.darkGray - static let Offset = CGSize.zero - static let Opacity: Float = 0.4 - static let Radius: CGFloat = 8 - - } - - } - - internal struct Animation { - - static let Duration = 0.15 - static let EntranceOptions: UIView.AnimationOptions = [.allowUserInteraction, .curveEaseOut] - static let ExitOptions: UIView.AnimationOptions = [.allowUserInteraction, .curveEaseIn] - static let DownScaleTransform = CGAffineTransform(scaleX: 0.9, y: 0.9) - - } - + static let TextFont = UIFont.systemFont(ofSize: 15) + static let BackgroundColor = UIColor(white: 0.94, alpha: 1) + static let SelectionBackgroundColor = UIColor(white: 0.89, alpha: 1) + static let SeparatorColor = UIColor.clear + static let SepratorInset = UIEdgeInsets.zero + static let CornerRadius: CGFloat = 2 + static let RowHeight: CGFloat = 44 + static let HeightPadding: CGFloat = 20 + + struct Shadow { + + static let Color = UIColor.darkGray + static let Offset = CGSize.zero + static let Opacity: Float = 0.4 + static let Radius: CGFloat = 8 + } + } + + internal struct Animation { + + static let Duration = 0.15 + static let EntranceOptions: UIView.AnimationOptions = [.allowUserInteraction, .curveEaseOut] + static let ExitOptions: UIView.AnimationOptions = [.allowUserInteraction, .curveEaseIn] + static let DownScaleTransform = CGAffineTransform(scaleX: 0.9, y: 0.9) + } } #endif + diff --git a/DropDown/helpers/DPDKeyboardListener.swift b/DropDown/helpers/DPDKeyboardListener.swift index d89c41c..515e3e6 100644 --- a/DropDown/helpers/DPDKeyboardListener.swift +++ b/DropDown/helpers/DPDKeyboardListener.swift @@ -11,30 +11,29 @@ import UIKit internal final class KeyboardListener { - + static let sharedInstance = KeyboardListener() - + fileprivate(set) var isVisible = false fileprivate(set) var keyboardFrame = CGRect.zero fileprivate var isListening = false - + deinit { stopListeningToKeyboard() } - } -//MARK: - Notifications +// MARK: - Notifications extension KeyboardListener { - + func startListeningToKeyboard() { if isListening { return } - + isListening = true - + NotificationCenter.default.addObserver( self, selector: #selector(keyboardWillShow(_:)), @@ -46,27 +45,26 @@ extension KeyboardListener { name: UIResponder.keyboardWillHideNotification, object: nil) } - + func stopListeningToKeyboard() { NotificationCenter.default.removeObserver(self) } - + @objc fileprivate func keyboardWillShow(_ notification: Notification) { isVisible = true keyboardFrame = keyboardFrame(fromNotification: notification) } - + @objc fileprivate func keyboardWillHide(_ notification: Notification) { isVisible = false keyboardFrame = keyboardFrame(fromNotification: notification) } - + fileprivate func keyboardFrame(fromNotification notification: Notification) -> CGRect { - return ((notification as NSNotification).userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect.zero + ((notification as NSNotification).userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect.zero } - } #endif diff --git a/DropDown/helpers/DPDUIView+Extension.swift b/DropDown/helpers/DPDUIView+Extension.swift index f29705d..547803b 100644 --- a/DropDown/helpers/DPDUIView+Extension.swift +++ b/DropDown/helpers/DPDUIView+Extension.swift @@ -10,41 +10,37 @@ import UIKit -//MARK: - Constraints +// MARK: - Constraints internal extension UIView { - + func addConstraints(format: String, options: NSLayoutConstraint.FormatOptions = [], metrics: [String: AnyObject]? = nil, views: [String: UIView]) { addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: options, metrics: metrics, views: views)) } - + func addUniversalConstraints(format: String, options: NSLayoutConstraint.FormatOptions = [], metrics: [String: AnyObject]? = nil, views: [String: UIView]) { addConstraints(format: "H:\(format)", options: options, metrics: metrics, views: views) addConstraints(format: "V:\(format)", options: options, metrics: metrics, views: views) } - } - - -//MARK: - Bounds +// MARK: - Bounds internal extension UIView { - + var windowFrame: CGRect? { - return superview?.convert(frame, to: nil) + superview?.convert(frame, to: nil) } - } internal extension UIWindow { - + static func visibleWindow() -> UIWindow? { var currentWindow = UIApplication.shared.keyWindow - + if currentWindow == nil { - let frontToBackWindows = Array(UIApplication.shared.windows.reversed()) - + let frontToBackWindows = Array(UIApplication.shared.windows.reversed()) + for window in frontToBackWindows { if window.windowLevel == UIWindow.Level.normal { currentWindow = window @@ -52,10 +48,9 @@ internal extension UIWindow { } } } - + return currentWindow } - } #endif diff --git a/DropDown/src/DropDown+Appearance.swift b/DropDown/src/DropDown+Appearance.swift index 7ac057d..504e3a2 100644 --- a/DropDown/src/DropDown+Appearance.swift +++ b/DropDown/src/DropDown+Appearance.swift @@ -26,10 +26,10 @@ extension DropDown { appearance.shadowRadius = DPDConstant.UI.Shadow.Radius appearance.animationduration = DPDConstant.Animation.Duration appearance.textColor = DPDConstant.UI.TextColor + appearance.separatorInset = DPDConstant.UI.SepratorInset appearance.selectedTextColor = DPDConstant.UI.SelectedTextColor appearance.textFont = DPDConstant.UI.TextFont } - } #endif diff --git a/DropDown/src/DropDown.swift b/DropDown/src/DropDown.swift index f41e8dd..e9bec00 100644 --- a/DropDown/src/DropDown.swift +++ b/DropDown/src/DropDown.swift @@ -20,32 +20,29 @@ private typealias ComputeLayoutTuple = (x: CGFloat, y: CGFloat, width: CGFloat, /// Can be `UIView` or `UIBarButtonItem`. @objc -public protocol AnchorView: class { +public protocol AnchorView: AnyObject { var plainView: UIView { get } - } extension UIView: AnchorView { public var plainView: UIView { - return self + self } - } extension UIBarButtonItem: AnchorView { public var plainView: UIView { - return value(forKey: "view") as! UIView + 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 + // TODO: handle iOS 7 landscape mode /// The dismiss mode for a drop down. public enum DismissMode { @@ -58,7 +55,6 @@ public final class DropDown: UIView { /// Not dismissable by the user. case manual - } /// The direction where the drop down will show from the `anchorView`. @@ -72,15 +68,14 @@ public final class DropDown: UIView { /// The drop down will show below or will not be showed if not enough space. case bottom - } - //MARK: - Properties + // 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: UI + // MARK: UI fileprivate let dismissableView = UIView() fileprivate let tableViewContainer = UIView() fileprivate let tableView = UITableView() @@ -102,7 +97,6 @@ public final class DropDown: UIView { return imgv }() - /// The view to which the drop down will displayed onto. public weak var anchorView: AnchorView? { didSet { setNeedsUpdateConstraints() } @@ -144,7 +138,7 @@ public final class DropDown: UIView { public var offsetFromWindowBottom = CGFloat(0) { didSet { setNeedsUpdateConstraints() } } - + /** The width of the drop down. @@ -171,13 +165,13 @@ public final class DropDown: UIView { } } - //MARK: Constraints + // MARK: Constraints fileprivate var heightConstraint: NSLayoutConstraint! fileprivate var widthConstraint: NSLayoutConstraint! fileprivate var xConstraint: NSLayoutConstraint! fileprivate var yConstraint: NSLayoutConstraint! - //MARK: Appearance + // MARK: Appearance @objc public dynamic var cellHeight = DPDConstant.UI.RowHeight { willSet { tableView.rowHeight = newValue } didSet { reloadAllComponents() } @@ -190,8 +184,8 @@ public final class DropDown: UIView { } } - public override var backgroundColor: UIColor? { - get { return tableViewBackgroundColor } + override public var backgroundColor: UIColor? { + get { tableViewBackgroundColor } set { tableViewBackgroundColor = newValue! } } @@ -219,6 +213,11 @@ public final class DropDown: UIView { didSet { reloadAllComponents() } } + @objc public dynamic var separatorInset = DPDConstant.UI.SepratorInset { + willSet { tableView.separatorInset = newValue } + didSet { reloadAllComponents() } + } + /** The corner radius of DropDown. @@ -302,17 +301,17 @@ public final class DropDown: UIView { 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. */ @@ -342,7 +341,7 @@ public final class DropDown: UIView { @objc public dynamic var selectedTextColor = DPDConstant.UI.SelectedTextColor { didSet { reloadAllComponents() } } - + /** The font of the text for each cells of the drop down. @@ -351,7 +350,7 @@ public final class DropDown: UIView { @objc public dynamic var textFont = DPDConstant.UI.TextFont { didSet { reloadAllComponents() } } - + /** The NIB to use for DropDownCells @@ -373,8 +372,8 @@ public final class DropDown: UIView { return Bundle(for: DropDownCell.self) #endif } - - //MARK: Content + + // MARK: Content /** The data source for the drop down. @@ -412,7 +411,7 @@ public final class DropDown: UIView { public var cellConfiguration: ConfigurationClosure? { didSet { reloadAllComponents() } } - + /** A advanced formatter for the cells. Allows customization when custom cells are used @@ -424,7 +423,7 @@ public final class DropDown: UIView { /// The action to execute when the user selects a cell. public var selectionAction: SelectionClosure? - + /** The action to execute when the user selects multiple cells. @@ -452,12 +451,12 @@ public final class DropDown: UIView { } fileprivate var minHeight: CGFloat { - return tableView.rowHeight + tableView.rowHeight } fileprivate var didSetupConstraints = false - //MARK: - Init's + // MARK: - Init's deinit { stopListeningToNotifications() @@ -507,10 +506,9 @@ public final class DropDown: UIView { super.init(coder: aDecoder) setup() } - } -//MARK: - Setup +// MARK: - Setup private extension DropDown { @@ -518,7 +516,7 @@ private extension DropDown { tableView.register(cellNib, forCellReuseIdentifier: DPDConstant.ReusableIdentifier.DropDownCell) DispatchQueue.main.async { - //HACK: If not done in dispatch_async on main queue `setupUI` will have no effect + // HACK: If not done in dispatch_async on main queue `setupUI` will have no effect self.updateConstraintsIfNeeded() self.setupUI() } @@ -531,7 +529,7 @@ private extension DropDown { tableView.delegate = self tableView.dataSource = self - + startListeningToKeyboard() accessibilityIdentifier = "drop_down" @@ -552,14 +550,13 @@ private extension DropDown { tableView.layer.cornerRadius = cornerRadius tableView.layer.masksToBounds = true } - } -//MARK: - UI +// MARK: - UI extension DropDown { - public override func updateConstraints() { + override public func updateConstraints() { if !didSetupConstraints { setupConstraints() } @@ -598,7 +595,6 @@ extension DropDown { addUniversalConstraints(format: "|[dismissableView]|", views: ["dismissableView": dismissableView]) - // Table view container addSubview(tableViewContainer) tableViewContainer.translatesAutoresizingMaskIntoConstraints = false @@ -650,7 +646,7 @@ extension DropDown { tableViewContainer.addUniversalConstraints(format: "|[tableView]|", views: ["tableView": tableView]) } - public override func layoutSubviews() { + override public func layoutSubviews() { super.layoutSubviews() // When orientation changes, layoutSubviews is called @@ -678,7 +674,7 @@ extension DropDown { bottomOffset = CGPoint(x: x, y: 0) } - + if anchorView == nil { layout = computeLayoutBottomDisplay(window: window) direction = .any @@ -687,27 +683,29 @@ extension DropDown { 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 @@ -716,27 +714,27 @@ extension DropDown { 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) } @@ -755,40 +753,40 @@ extension DropDown { 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 { @@ -797,21 +795,20 @@ extension DropDown { } } } - + fileprivate func constraintWidthToFittingSizeIfNecessary(layout: inout ComputeLayoutTuple) { guard width == nil else { return } - + if layout.width < fittingWidth() { layout.width = fittingWidth() } } - } -//MARK: - Actions +// MARK: - Actions extension DropDown { - + /** An Objective-C alias for the show() method which converts the returned tuple into an NSDictionary. @@ -820,16 +817,16 @@ extension DropDown { @objc(show) public func objc_show() -> NSDictionary { let (canBeDisplayed, offScreenHeight) = show() - + var info = [AnyHashable: Any]() info["canBeDisplayed"] = canBeDisplayed if let offScreenHeight = offScreenHeight { info["offScreenHeight"] = offScreenHeight } - + return NSDictionary(dictionary: info) } - + /** Shows the drop down if enough height. @@ -866,11 +863,11 @@ extension DropDown { } isHidden = false - + if anchorPoint != nil { tableViewContainer.layer.anchorPoint = anchorPoint! } - + if transform != nil { tableViewContainer.transform = transform! } else { @@ -891,17 +888,18 @@ extension DropDown { accessibilityViewIsModal = true UIAccessibility.post(notification: .screenChanged, argument: self) - //deselectRows(at: selectedRowIndices) + // deselectRows(at: selectedRowIndices) selectRows(at: selectedRowIndices) return (layout.canBeDisplayed, layout.offscreenHeight) } - public override func accessibilityPerformEscape() -> Bool { + override public func accessibilityPerformEscape() -> Bool { switch dismissMode { case .automatic, .onTap: cancel() return true + case .manual: return false } @@ -929,7 +927,7 @@ extension DropDown { animations: { [weak self] in self?.setHiddentState() }, - completion: { [weak self] finished in + completion: { [weak self] _ in guard let `self` = self else { return } self.isHidden = true @@ -951,10 +949,9 @@ extension DropDown { alpha = 1 tableViewContainer.transform = CGAffineTransform.identity } - } -//MARK: - UITableView +// MARK: - UITableView extension DropDown { @@ -984,12 +981,12 @@ extension DropDown { selectedRowIndices.removeAll() } } - + public func selectRows(at indices: Set?) { indices?.forEach { selectRow(at: $0) } - + // if we are in multi selection mode then reload data so that all selections are shown if multiSelectionAction != nil { tableView.reloadData() @@ -997,10 +994,9 @@ extension DropDown { } public func deselectRow(at index: Index?) { - guard let index = index - , index >= 0 + guard let index = index, index >= 0 else { return } - + // remove from indices if let selectedRowIndex = selectedRowIndices.firstIndex(where: { $0 == index }) { selectedRowIndices.remove(at: selectedRowIndex) @@ -1008,7 +1004,7 @@ extension DropDown { tableView.deselectRow(at: IndexPath(row: index, section: 0), animated: true) } - + // de-selects the rows at the indices provided public func deselectRows(at indices: Set?) { indices?.forEach { @@ -1018,7 +1014,7 @@ extension DropDown { /// Returns the index of the selected row. public var indexForSelectedRow: Index? { - return (tableView.indexPathForSelectedRow as NSIndexPath?)?.row + (tableView.indexPathForSelectedRow as NSIndexPath?)?.row } /// Returns the selected item. @@ -1030,33 +1026,33 @@ extension DropDown { /// Returns the height needed to display all cells. fileprivate var tableHeight: CGFloat { - return tableView.rowHeight * CGFloat(dataSource.count) + tableView.rowHeight * CGFloat(dataSource.count) } - //MARK: Objective-C methods for converting the Swift type Index + // MARK: Objective-C methods for converting the Swift type Index @objc public func selectRow(_ index: Int, scrollPosition: UITableView.ScrollPosition = .none) { - self.selectRow(at:Index(index), scrollPosition: scrollPosition) + self.selectRow(at: Index(index), scrollPosition: scrollPosition) } - + @objc public func clearSelection() { - self.selectRow(at:nil) + self.selectRow(at: nil) } - + @objc public func deselectRow(_ index: Int) { tableView.deselectRow(at: IndexPath(row: Index(index), section: 0), animated: true) } @objc public var indexPathForSelectedRow: NSIndexPath? { - return tableView.indexPathForSelectedRow as NSIndexPath? + tableView.indexPathForSelectedRow as NSIndexPath? } } -//MARK: - UITableViewDataSource - UITableViewDelegate +// MARK: - UITableViewDataSource - UITableViewDelegate extension DropDown: UITableViewDataSource, UITableViewDelegate { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return dataSource.count + dataSource.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -1067,35 +1063,34 @@ extension DropDown: UITableViewDataSource, UITableViewDelegate { 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 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) { - cell.isSelected = selectedRowIndices.first{ $0 == (indexPath as NSIndexPath).row } != nil + cell.isSelected = selectedRowIndices.first { $0 == (indexPath as NSIndexPath).row } != nil } public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let selectedRowIndex = (indexPath as NSIndexPath).row - - + // are we in multi-selection mode? if let multiSelectionCallback = multiSelectionAction { // if already selected then deselect @@ -1112,35 +1107,33 @@ extension DropDown: UITableViewDataSource, UITableViewDelegate { let selectedRowIndicesArray = Array(selectedRowIndices) let selectedRows = selectedRowIndicesArray.map { dataSource[$0] } - + selectionAction?(selectedRowIndex, dataSource[selectedRowIndex]) multiSelectionCallback(selectedRowIndicesArray, selectedRows) tableView.reloadData() return } } - + // Perform single selection logic selectedRowIndices.removeAll() selectedRowIndices.insert(selectedRowIndex) selectionAction?(selectedRowIndex, dataSource[selectedRowIndex]) - + if let _ = anchorView as? UIBarButtonItem { // DropDown's from UIBarButtonItem are menus so we deselect the selected menu right after selection deselectRow(at: selectedRowIndex) } - + hide() - } - } -//MARK: - Auto dismiss +// MARK: - Auto dismiss extension DropDown { - public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let view = super.hitTest(point, with: event) if dismissMode == .automatic && view === dismissableView { @@ -1155,10 +1148,9 @@ extension DropDown { fileprivate func dismissableViewTapped() { cancel() } - } -//MARK: - Keyboard events +// MARK: - Keyboard events extension DropDown { @@ -1193,7 +1185,6 @@ extension DropDown { fileprivate func keyboardUpdate() { self.setNeedsUpdateConstraints() } - } private extension DispatchQueue { diff --git a/DropDown/src/DropDownCell.swift b/DropDown/src/DropDownCell.swift index bf49947..98c8a73 100644 --- a/DropDown/src/DropDownCell.swift +++ b/DropDown/src/DropDownCell.swift @@ -11,42 +11,41 @@ import UIKit open class DropDownCell: UITableViewCell { - - //UI + + // UI @IBOutlet open weak var optionLabel: UILabel! - + var selectedBackgroundColor: UIColor? var highlightTextColor: UIColor? var normalTextColor: UIColor? - } -//MARK: - UI +// MARK: - UI extension DropDownCell { - + 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 } @@ -61,7 +60,7 @@ extension DropDownCell { } } } - + if animated { UIView.animate(withDuration: 0.3, animations: { executeSelection() @@ -72,7 +71,6 @@ extension DropDownCell { accessibilityTraits = selected ? .selected : .none } - } #endif