diff --git a/Demo/Base.lproj/Main.storyboard b/Demo/Base.lproj/Main.storyboard index 364b07e..415ba85 100644 --- a/Demo/Base.lproj/Main.storyboard +++ b/Demo/Base.lproj/Main.storyboard @@ -1,13 +1,11 @@ - + - - - + @@ -16,160 +14,174 @@ - - + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - + @@ -203,7 +215,7 @@ - + @@ -213,7 +225,7 @@ - + diff --git a/Demo/CheckboxCell.swift b/Demo/CheckboxCell.swift new file mode 100644 index 0000000..c1f2f7e --- /dev/null +++ b/Demo/CheckboxCell.swift @@ -0,0 +1,42 @@ +// +// CheckboxCell.swift +// Demo +// +// Created by Aleksei Cherepanov on 31/10/2018. +// Copyright © 2018 Kevin Hirsch. All rights reserved. +// + +import UIKit +import DropDown + +class CheckboxCell: DropDownCustomCell { + + override func setSelected(_ selected: Bool, animated: Bool) { + let executeSelection: () -> Void = { [weak self] in + guard let `self` = self else { return } + self.accessoryType = selected ? .checkmark : .none + } + + if animated { + UIView.animate(withDuration: 0.3, animations: { + executeSelection() + }) + } else { + executeSelection() + } + accessibilityTraits = selected ? .selected : .none + } + + override func systemLayoutSizeFitting(_ targetSize: CGSize) -> CGSize { + let contentSize = optionLabel.systemLayoutSizeFitting(targetSize) + let accessoryViewWidth:CGFloat = 40 + return CGSize(width: contentSize.width + accessoryViewWidth, + height: contentSize.height) + } + + override func layoutSubviews() { + super.layoutSubviews() + optionLabel.frame = contentView.bounds + } + +} diff --git a/Demo/MySecondCell.swift b/Demo/MySecondCell.swift new file mode 100644 index 0000000..46e8551 --- /dev/null +++ b/Demo/MySecondCell.swift @@ -0,0 +1,39 @@ +// +// MySecondCell.swift +// Demo +// +// Created by Aleksei Cherepanov on 31/10/2018. +// Copyright © 2018 Kevin Hirsch. All rights reserved. +// + +import UIKit +import DropDown + +class MySecondCell: DropDownCustomCell { + var logoImageView = UIImageView(frame: .zero) + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + contentView.addSubview(logoImageView) + } + + override func layoutSubviews() { + super.layoutSubviews() + let size = contentView.bounds.size + let imageSize = logoImageView.image?.size ?? .zero + let padding: CGFloat = 4 + logoImageView.frame = CGRect(x: padding, + y: (frame.height - imageSize.height)/2, + width: imageSize.width, + height: imageSize.height) + let offset = padding * 2 + imageSize.width + optionLabel.frame = CGRect(x: offset, + y: 0, + width: size.width - offset, + height: size.height) + } +} diff --git a/Demo/ViewController.swift b/Demo/ViewController.swift index c5130f0..42f5743 100644 --- a/Demo/ViewController.swift +++ b/Demo/ViewController.swift @@ -81,6 +81,8 @@ class ViewController: UIViewController { switch sender.selectedSegmentIndex { case 0: setupDefaultDropDown() case 1: customizeDropDown(self) + case 2: customizeDropDownWithClass(self) + case 3: customizeDropDownSelector(self) default: break; } } @@ -134,6 +136,31 @@ class ViewController: UIViewController { /*** ---------------- ***/ } } + + func customizeDropDownWithClass(_ sender: AnyObject) { + dropDowns.forEach { + /*** FOR CUSTOM CELLS ***/ + $0.cellClass = MySecondCell.self + + $0.customCellConfiguration = { (index: Index, item: String, cell: DropDownCell) -> Void in + guard let cell = cell as? MySecondCell else { return } + + // Setup your custom UI components + cell.logoImageView.image = UIImage(named: "logo_\(index % 10)") + } + /*** ---------------- ***/ + } + } + + func customizeDropDownSelector(_ sender: AnyObject) { + dropDowns.forEach { + /*** FOR CUSTOM CELLS ***/ + $0.cellClass = CheckboxCell.self + $0.customCellConfiguration = nil + } + } + + //MARK: - UIViewController @@ -143,6 +170,8 @@ class ViewController: UIViewController { setupDropDowns() dropDowns.forEach { $0.dismissMode = .onTap } dropDowns.forEach { $0.direction = .any } + + dropDowns.forEach { $0.showArrowIndicator = true } view.addSubview(textField) } diff --git a/DropDown.xcodeproj/project.pbxproj b/DropDown.xcodeproj/project.pbxproj index 3b60491..fc3fecf 100644 --- a/DropDown.xcodeproj/project.pbxproj +++ b/DropDown.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 0AB5D89A1D0EF15D002D3A17 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB5D8971D0EF15D002D3A17 /* ViewController.swift */; }; 0AB5D8A11D0EF173002D3A17 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0AB5D89F1D0EF173002D3A17 /* Main.storyboard */; }; 0AC1C33A1B6B884100A8DC0D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0AC1C3391B6B884100A8DC0D /* Images.xcassets */; }; + 9442B7DB2189B8EC000720B8 /* CheckboxCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9442B7DA2189B8EC000720B8 /* CheckboxCell.swift */; }; + 945A86E22188BD39007B8D96 /* MySecondCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945A86E12188BD39007B8D96 /* MySecondCell.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -81,6 +83,8 @@ 0AB5D8971D0EF15D002D3A17 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 0AB5D8A01D0EF173002D3A17 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 0AC1C3391B6B884100A8DC0D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Demo/Images.xcassets; sourceTree = ""; }; + 9442B7DA2189B8EC000720B8 /* CheckboxCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxCell.swift; sourceTree = ""; }; + 945A86E12188BD39007B8D96 /* MySecondCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySecondCell.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -137,6 +141,8 @@ 0AB5D8971D0EF15D002D3A17 /* ViewController.swift */, 0A8518D41D6480190089529A /* MyCell.swift */, 0A8518D61D6481E30089529A /* MyCell.xib */, + 945A86E12188BD39007B8D96 /* MySecondCell.swift */, + 9442B7DA2189B8EC000720B8 /* CheckboxCell.swift */, 0AB5D89F1D0EF173002D3A17 /* Main.storyboard */, 0AB5D8961D0EF15D002D3A17 /* NiceButton.swift */, 0A7644121B676C2300BF1A2D /* Supporting Files */, @@ -358,8 +364,10 @@ files = ( 0AB5D89A1D0EF15D002D3A17 /* ViewController.swift in Sources */, 0A8518D51D6480190089529A /* MyCell.swift in Sources */, + 9442B7DB2189B8EC000720B8 /* CheckboxCell.swift in Sources */, 0AB5D8991D0EF15D002D3A17 /* NiceButton.swift in Sources */, 0AB5D8981D0EF15D002D3A17 /* AppDelegate.swift in Sources */, + 945A86E22188BD39007B8D96 /* MySecondCell.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/DropDown/helpers/DPDConstants.swift b/DropDown/helpers/DPDConstants.swift index b5caa30..68f40d0 100644 --- a/DropDown/helpers/DPDConstants.swift +++ b/DropDown/helpers/DPDConstants.swift @@ -30,6 +30,7 @@ internal struct DPDConstant { 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 SeparatorInset = UIEdgeInsets.zero static let CornerRadius: CGFloat = 2 static let RowHeight: CGFloat = 44 static let HeightPadding: CGFloat = 20 diff --git a/DropDown/src/DropDown.swift b/DropDown/src/DropDown.swift index 42738fa..401e6ef 100644 --- a/DropDown/src/DropDown.swift +++ b/DropDown/src/DropDown.swift @@ -159,15 +159,21 @@ public final class DropDown: UIView { */ public var arrowIndicationX: CGFloat? { didSet { - if let arrowIndicationX = arrowIndicationX { - tableViewContainer.addSubview(arrowIndication) - arrowIndication.tintColor = tableViewBackgroundColor - arrowIndication.frame.origin.x = arrowIndicationX - } else { - arrowIndication.removeFromSuperview() - } + guard let x = arrowIndicationX else { return } + arrowIndication.frame.origin.x = x + showArrowIndicator = true } } + public var showArrowIndicator: Bool = false { + didSet { + if showArrowIndicator { + tableViewContainer.addSubview(arrowIndication) + arrowIndication.tintColor = tableViewBackgroundColor + } else { + arrowIndication.removeFromSuperview() + } + } + } //MARK: Constraints fileprivate var heightConstraint: NSLayoutConstraint! @@ -184,7 +190,7 @@ public final class DropDown: UIView { @objc fileprivate dynamic var tableViewBackgroundColor = DPDConstant.UI.BackgroundColor { willSet { tableView.backgroundColor = newValue - if arrowIndicationX != nil { arrowIndication.tintColor = newValue } + if showArrowIndicator { arrowIndication.tintColor = newValue } } } @@ -216,6 +222,16 @@ public final class DropDown: UIView { willSet { tableView.separatorColor = newValue } didSet { reloadAllComponents() } } + + /** + The separator inset + + Changing the separator inset automatically reloads the drop down. + */ + @objc public dynamic var separatorInset = DPDConstant.UI.SeparatorInset { + willSet { tableView.separatorInset = newValue } + didSet { reloadAllComponents() } + } /** The corner radius of DropDown. @@ -362,6 +378,14 @@ public final class DropDown: UIView { reloadAllComponents() } } + + public var cellClass: DropDownCustomCell.Type = DropDownCustomCell.self { + didSet { + tableView.register(cellClass, forCellReuseIdentifier: DPDConstant.ReusableIdentifier.DropDownCell) + templateCell = nil + reloadAllComponents() + } + } //MARK: Content @@ -538,6 +562,7 @@ private extension DropDown { tableView.backgroundColor = tableViewBackgroundColor tableView.separatorColor = separatorColor + tableView.separatorInset = separatorInset tableView.layer.cornerRadius = cornerRadius tableView.layer.masksToBounds = true } @@ -693,20 +718,25 @@ extension DropDown { direction = .top } } - + constraintWidthToFittingSizeIfNecessary(layout: &layout) constraintWidthToBoundsIfNecessary(layout: &layout, in: window) let visibleHeight = tableHeight - layout.offscreenHeight let canBeDisplayed = visibleHeight >= minHeight + if showArrowIndicator { + arrowIndication.frame.origin.x = computeArrowIndicator(window: window, + layout: layout) + } + 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 width = self.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) @@ -749,16 +779,27 @@ extension DropDown { return (x, y, width, offscreenHeight) } + + fileprivate func computeArrowIndicator(window: UIWindow, layout: ComputeLayoutTuple) -> CGFloat { + if let x = arrowIndicationX { return x } + guard let anchorViewX = anchorView?.plainView.windowFrame?.minX else { + return layout.width / 2 + } + let anchorViewWidth = anchorView?.plainView.bounds.width ?? 0 + let leftSpacing = max(anchorViewX - layout.x, 0) + return leftSpacing + anchorViewWidth/2 + } fileprivate func fittingWidth() -> CGFloat { if templateCell == nil { - templateCell = (cellNib.instantiate(withOwner: nil, options: nil)[0] as! DropDownCell) + templateCell = (tableView.dequeueReusableCell(withIdentifier: DPDConstant.ReusableIdentifier.DropDownCell) as! DropDownCell) } var maxWidth: CGFloat = 0 for index in 0..