diff --git a/CustomDropDown.xcodeproj/project.pbxproj b/CustomDropDown.xcodeproj/project.pbxproj index 2679193..5fcf6fc 100644 --- a/CustomDropDown.xcodeproj/project.pbxproj +++ b/CustomDropDown.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 172E2CB6254254A5001F37B7 /* DropDownMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E2CB5254254A5001F37B7 /* DropDownMode.swift */; }; 172E2CBF25425537001F37B7 /* CustomDropDownDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E2CBE25425537001F37B7 /* CustomDropDownDataSource.swift */; }; 172E2CC32542556A001F37B7 /* CustomDropDownDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E2CC22542556A001F37B7 /* CustomDropDownDelegate.swift */; }; + 27EDDDEA2546A17800DB0ECA /* UIWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27EDDDE92546A17800DB0ECA /* UIWindowExtension.swift */; }; 3D469C3D2545DDFB00B2993A /* ShadowAndCornerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D469C3C2545DDFB00B2993A /* ShadowAndCornerConfig.swift */; }; 600DFEAB2529BA4200206E64 /* CustomDropDown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 600DFEAA2529BA4200206E64 /* CustomDropDown.swift */; platformFilter = ios; }; 6055FCBC2529AEC80096A0FD /* CustomDropDown.h in Headers */ = {isa = PBXBuildFile; fileRef = 6055FCBA2529AEC80096A0FD /* CustomDropDown.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -41,6 +42,7 @@ 172E2CB5254254A5001F37B7 /* DropDownMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropDownMode.swift; sourceTree = ""; }; 172E2CBE25425537001F37B7 /* CustomDropDownDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDropDownDataSource.swift; sourceTree = ""; }; 172E2CC22542556A001F37B7 /* CustomDropDownDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDropDownDelegate.swift; sourceTree = ""; }; + 27EDDDE92546A17800DB0ECA /* UIWindowExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIWindowExtension.swift; sourceTree = ""; }; 3D469C3C2545DDFB00B2993A /* ShadowAndCornerConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowAndCornerConfig.swift; sourceTree = ""; }; 600DFEAA2529BA4200206E64 /* CustomDropDown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDropDown.swift; sourceTree = ""; }; 6055FCB72529AEC80096A0FD /* CustomDropDown.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CustomDropDown.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -100,6 +102,7 @@ isa = PBXGroup; children = ( 172E2CAB254251AC001F37B7 /* UIViewExtension.swift */, + 27EDDDE92546A17800DB0ECA /* UIWindowExtension.swift */, ); path = Extensions; sourceTree = ""; @@ -301,6 +304,7 @@ 172E2CB225425475001F37B7 /* DropDownConfig.swift in Sources */, 172E2C9425423F7B001F37B7 /* DropDownImageLabelView.swift in Sources */, 6055FCDA2529BF290096A0FD /* CustomDropDownPresenter.swift in Sources */, + 27EDDDEA2546A17800DB0ECA /* UIWindowExtension.swift in Sources */, 172E2CB6254254A5001F37B7 /* DropDownMode.swift in Sources */, 600DFEAB2529BA4200206E64 /* CustomDropDown.swift in Sources */, 172E2CA325425123001F37B7 /* ImageLabelData.swift in Sources */, diff --git a/CustomDropDown/Extensions/UIWindowExtension.swift b/CustomDropDown/Extensions/UIWindowExtension.swift new file mode 100644 index 0000000..45183eb --- /dev/null +++ b/CustomDropDown/Extensions/UIWindowExtension.swift @@ -0,0 +1,27 @@ +// +// UIWindowExtension.swift +// CustomDropDown +// +// Created by Franklin Samboni on 26/10/20. +// Copyright © 2020 Amirthy Tejeshwar. All rights reserved. +// + +import UIKit + +extension UIWindow { + func topViewController() -> UIViewController? { + var top = self.rootViewController + while true { + if let presented = top?.presentedViewController { + top = presented + } else if let nav = top as? UINavigationController { + top = nav.visibleViewController + } else if let tab = top as? UITabBarController { + top = tab.selectedViewController + } else { + break + } + } + return top + } +} diff --git a/CustomDropDown/View/CustomDropDownView.swift b/CustomDropDown/View/CustomDropDownView.swift index 1718dee..dd02d42 100644 --- a/CustomDropDown/View/CustomDropDownView.swift +++ b/CustomDropDown/View/CustomDropDownView.swift @@ -8,7 +8,7 @@ import UIKit -class CustomDropDownView: UIView, UITableViewDataSource, UITableViewDelegate { +class CustomDropDownView: UIView, UITableViewDataSource, UITableViewDelegate, UIGestureRecognizerDelegate { // MARK: - Constants @@ -22,8 +22,9 @@ class CustomDropDownView: UIView, UITableViewDataSource, UITableViewDelegate var dropDown: CustomDropDown? var dropDownDisplayView: UIView! var heightConstraint: NSLayoutConstraint = NSLayoutConstraint() - var isOpen: Bool = true + var isOpen: Bool = false var identifier: Int + var outsideGesture: UITapGestureRecognizer? // MARK: - Life cycle @@ -121,26 +122,36 @@ class CustomDropDownView: UIView, UITableViewDataSource, UITableViewDelegate } private func toggleDropDown() { - guard let dropDown = self.dropDown else { return } - if isOpen { - isOpen = false - setHeightConstraint(to: dropDownHeight, maxHeight: dropDown.tableView.contentSize.height) - superview?.bringSubviewToFront(dropDown) - animateDropDown(animations: { - dropDown.layoutIfNeeded() - dropDown.center.y += dropDown.frame.height/2 - }) + closeDropDown() } else { - isOpen = true - setHeightConstraint(to: 0) - animateDropDown(animations: { - dropDown.center.y -= dropDown.frame.height/2 - dropDown.layoutIfNeeded() - }) + openDropDown() } } + private func openDropDown() { + guard let dropDown = self.dropDown else { return } + isOpen = true + addOutsideGesture() + setHeightConstraint(to: dropDownHeight, maxHeight: dropDown.tableView.contentSize.height) + superview?.bringSubviewToFront(dropDown) + animateDropDown(animations: { + dropDown.layoutIfNeeded() + dropDown.center.y += dropDown.frame.height/2 + }) + } + + private func closeDropDown() { + guard let dropDown = self.dropDown else { return } + isOpen = false + removeOutsideGesture() + setHeightConstraint(to: 0) + animateDropDown(animations: { + dropDown.center.y -= dropDown.frame.height/2 + dropDown.layoutIfNeeded() + }) + } + private func setHeightConstraint(to height: CGFloat, maxHeight: CGFloat? = nil) { var height: CGFloat = height if let maxHeight = maxHeight, height > maxHeight { @@ -162,6 +173,34 @@ class CustomDropDownView: UIView, UITableViewDataSource, UITableViewDelegate completion: completion) } + private func addOutsideGesture() { + guard outsideGesture == nil else { return } + let rootView = window?.topViewController()?.view + outsideGesture = UITapGestureRecognizer(target: self, action: #selector(outsideGestureHandler)) + outsideGesture!.cancelsTouchesInView = false + outsideGesture!.delegate = self + rootView?.addGestureRecognizer(outsideGesture!) + } + + private func removeOutsideGesture() { + let rootView = window?.topViewController()?.view + if let tap = outsideGesture { + rootView?.removeGestureRecognizer(tap) + } + outsideGesture = nil + } + + // This func is called even when user click on tableview row. So, to avoid conflicts + // is necessary to detect if tap is not over DropDown to close it. + @objc func outsideGestureHandler(gesture: UITapGestureRecognizer) { + let location = gesture.location(in: dropDown) + let wh = (w: dropDown?.frame.width ?? 0, h: dropDown?.frame.height ?? 0) + let tapOutsiteOfDropDown = (location.x < 0 || location.y < 0 || location.x > wh.w || location.y > wh.h) + if isOpen && tapOutsiteOfDropDown { + closeDropDown() + } + } + // MARK: - Table View private func getImageLabelCell(tableView: UITableView, indexPath: IndexPath) -> UITableViewCell { @@ -201,6 +240,12 @@ class CustomDropDownView: UIView, UITableViewDataSource, UITableViewDelegate return cell } + // MARK: UIGestureRecognizerDelegate methods + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } + // MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {