diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1ba898 --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +## Navigate + +- [Features](#features) +- [Installation](#installation) +- [Usage](#usage) +- [Delegate](#delegate) + +## Features + +- Highly customizable + - support dark/light theme + - cornerRadius + - width (iPad) + + ### Item config + + Item type + ```swift + - ActionCell + - Title + - Spacer + - InfoCell + - TextFieldCell + - SwitchCell + - SegmentCell + ``` + + Item color + ```swift + - standard + - clear + - filled + - tinted + - custom + ``` + + Item Layout + ```swift + - Icon_Title (Icon left, title right) + - Title_Icon (Title left, Icon right) + ``` + + Spacer type + ```swift + - empty + - line + - dashedLine + - divider + ``` + +## Installation +Put `Sources` folder in your Xcode project. Make sure to enable `Copy items if needed`. + +## Usage + +```swift +let actions: [UIFloatMenuAction] = [ + .init(item: .Title("Title")), + .init(item: .SegmentCell(items: ["Item 1", UIImage(systemName: "bookmark")!, "Item 3"], selected: 1, action: #selector(segmentAction))), + .init(item: .SwitchCell(icon: UIImage(systemName: "bookmark")!, title: "Switch 1", action: #selector(switchAction))), + .init(item: .InfoCell(icon: UIImage(systemName: "questionmark.square")!, title: "Data title", label: .config(fontSize: 15, fontWeight: .semibold))), + .init(item: .Spacer(type: .divider)), + .init(item: .Title("Title")), + .init(item: .ActionCell(icon: UIImage(systemName: "arrow.down.square.fill")!, title: "Title", layout: .Icon_Title), itemColor: .tinted(.systemBlue), action: { _ in + print("Action") + }), + .init(item: .Spacer(type: .line())), + .init(item: .ActionCell(icon: UIImage(systemName: "arrow.right.square.fill")!, title: "Title", subtitle: "Test subtitle", layout: .Icon_Title), itemColor: .filled(.systemPurple), action: { _ in + print("Action") + }) +] + +let menu = UIFloatMenu.setup(actions: actions) +menu.header.title = "UIFloatMenu title" +menu.header.subTitle = "UIFloatMenu subtitle" +menu.header.showHeader = true +menu.header.showLine = true +menu.config.cornerRadius = 12 +menu.config.blurBackground = true +menu.config.viewWidth_iPad = 350 +menu.config.presentation = .default +menu.closeDelegate = self +menu.show(self) +``` + +## Delegate + +To `know when menu is closed`, set the delegate with protocol `UIFloatMenuCloseDelegate`: + +```swift +func didCloseMenu() { + print("didCloseMenu - MenuClosed") +} +``` + +To get `UITextField data`, set the delegate with protocol `UIFloatMenuTextFieldDelegate`: + +```swift +func getTextFieldData(_ data: [String]) { + print("TextField -", data) +} +``` diff --git a/Sources/UIFloatMenu.swift b/Sources/UIFloatMenu.swift new file mode 100644 index 0000000..b4ee0c9 --- /dev/null +++ b/Sources/UIFloatMenu.swift @@ -0,0 +1,349 @@ +// +// UIFloatMenu.swift +// UIFloatMenu +// + +import UIKit + +class UIFloatMenu { + + let shared = UIFloatMenu() + + static public func setup(actions: [UIFloatMenuAction]) -> UIFloatMenuController { + let vc = UIFloatMenuController() + vc.actions = actions + return vc + } + + static private var initY = [CGFloat]() + + static public var currentVC = UIViewController() + + static public var viewConfig = UIFloatMenuConfig() + static public var headerConfig = UIFloatMenuHeaderConfig() + + static private var keyboardHelper: KeyboardHelper? + + //MARK: - Config + // Max view to show + static private var maxView: Int = 3 + + // Animation duration + static private var animationDuration: TimeInterval = 0.3 + + // Delegate + static var closeDelegate: UIFloatMenuCloseDelegate? + static var textFieldDelegate: UIFloatMenuTextFieldDelegate? + + // Queue + static private var queue = [UIFloatMenuQueue]() + + //MARK: - Show + static func show(_ vc: UIViewController, actions: [UIFloatMenuAction]) { + if queue.count <= maxView { + let correct = correctPosition(viewConfig.presentation) + let id = UIFloatMenuID.genUUID(queue.count) + + let menuView = UIFloatMenuView.init(items: actions, vc: currentVC, header: headerConfig, config: viewConfig, + closeDelegate: closeDelegate, textFieldDelegate: textFieldDelegate) + menuView.tag = id + + vc.view.addSubview(menuView) + + let pan = UIPanGestureRecognizer(target: self, action: #selector(UIControlViewDrag(_:))) + pan.maximumNumberOfTouches = 1 + pan.cancelsTouchesInView = true + menuView.addGestureRecognizer(pan) + + if case .default = correct { + for gesture in menuView.gestureRecognizers! { + gesture.isEnabled = true + } + } else { + for gesture in menuView.gestureRecognizers! { + gesture.isEnabled = false + } + } + + let customConfig = UIFloatMenuConfig(cornerRadius: viewConfig.cornerRadius, blurBackground: viewConfig.blurBackground, + viewWidth_iPad: viewConfig.viewWidth_iPad) + + queue.append(.init(uuid: id, viewHeight: menuView.bounds.height, config: customConfig, actions: actions)) + currentVC = vc + + keyboardHelper = KeyboardHelper { animation, keyboardFrame, duration in + switch animation { + case .keyboardWillShow: + let yValue = (menuView.frame.size.height/2.2)-(UIFloatMenuHelper.getPadding(.bottom)*2.4) + menuView.transform = CGAffineTransform(translationX: 0, y: -yValue) + case .keyboardWillHide: + menuView.transform = .identity + } + } + + showTo(menuView, positions: correct) + + initY.append(menuView.frame.origin.y) + } else { + print("Max view count") + } + } + + //MARK: - closeMenu + static public func closeMenu(slideAnimation: Bool = true, completion: @escaping () -> Void) { + if let UIFloatMenu = currentVC.view.viewWithTag(UIFloatMenuID.backViewID) { + UIView.animate(withDuration: 0.2, animations: { + closeMenu(UIFloatMenu) + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + completion() + } + }, completion: { (finished: Bool) in + UIFloatMenu.removeFromSuperview() + }) + } + + queue.removeAll() + initY.removeAll() + } + + //MARK: - UIControlViewDrag + // MARK: - Test + @objc static private func UIControlViewDrag(_ sender: UIPanGestureRecognizer) { + let appRect = UIApplication.shared.windows[0].bounds + let topPadding = UIFloatMenuHelper.getPadding(.top) + let bottomPadding = UIFloatMenuHelper.getPadding(.bottom) + + let screen = topPadding + appRect.height + bottomPadding + + var dismissDragSize: CGFloat { + return bottomPadding.isZero ? screen - (topPadding*4.5) : screen - (bottomPadding*4) + } + + switch sender.state { + case .changed: + panChanged(sender) + case .ended, .cancelled: + panEnded(sender, dismissDragSize: dismissDragSize) + case .failed, .possible: + break + @unknown default: + break + } + } + + // MARK: - panChanged() + static private func panChanged(_ gesture: UIPanGestureRecognizer) { + let view = gesture.view! + let translation = gesture.translation(in: gesture.view) + let velocity = gesture.velocity(in: gesture.view) + + var translationAmount = translation.y >= 0 ? translation.y : -pow(abs(translation.y), 0.7) + + let rubberBanding = true + + if !rubberBanding && translationAmount < 0 { translationAmount = 0 } + + if gesture.direction(in: view) == .Up && gesture.view!.frame.origin.y < initY.last! { + if let UIFloatMenu = currentVC.view.viewWithTag(queue.last!.uuid!) { + let backTranslationAmount = translation.y >= 0 ? translation.y : -pow(abs(translation.y), 0.6) + UIFloatMenu.transform = CGAffineTransform(translationX: 0, y: backTranslationAmount) + } + } + + if gesture.direction(in: view) == .Down { + for order in 0.. 180 { + UIView.animate(withDuration: 0.2, animations: { + UIFloatMenu.transform = .identity + }) + } else { + if UIFloatMenu.frame.origin.y <= initY[order] { + let backTranslationAmount = translation.y >= 0 ? translation.y : -pow(abs(translation.y), 0.6) + UIFloatMenu.transform = CGAffineTransform(translationX: 0, y: backTranslationAmount) + } + } + } + } + } + + view.transform = CGAffineTransform(translationX: 0, y: translationAmount) + } + + // MARK: - panEnded() + static private func panEnded(_ gesture: UIPanGestureRecognizer, dismissDragSize: CGFloat) { + let velocity = gesture.velocity(in: gesture.view).y + if ((gesture.view!.frame.origin.y+40) >= dismissDragSize) || (velocity > 180) { + NotificationCenter.default.post(name: NSNotification.Name("UIMenuClose"), object: nil) + } else { + UIView.animate(withDuration: 0.2, animations: { + if let UIFloatMenu = currentVC.view.viewWithTag(queue.last!.uuid!) { + UIFloatMenu.transform = .identity + } + }) + } + } + + static public func correctPosition(_ position: UIFloatMenuPresentStyle) -> UIFloatMenuPresentStyle { + let device = UIDevice.current.userInterfaceIdiom + + if device == .pad { + let layout = Layout.determineLayout() + + if layout == .iPadOneThirdScreen { + if case .center = position { + return .center + } + return .default + } + return position + } else if device == .phone { + if case .center = position { + return .center + } + return .default + } else { + return position + } + } + + private enum presentPosition { + case prepare + case show + case close + } + + // MARK: - closeMenu() + static private func closeMenu(_ menuView: UIView) { + let correct = correctPosition(viewConfig.presentation) + let appRect = UIApplication.shared.windows[0].bounds + let topPadding = UIFloatMenuHelper.getPadding(.top) + + var getClose: CGFloat { + return appRect.height + queue[0].viewHeight + (topPadding) + } + + switch correct { + case .default: + menuView.layer.position.y = getClose + case .center: + menuView.alpha = 0 + case .leftDown: + menuView.alpha = 0 + break + case .leftUp: + menuView.alpha = 0 + break + case .rightUp: + menuView.alpha = 0 + break + case .rightDown: + menuView.alpha = 0 + break + } + } + + // MARK: - showTo() + static public func showTo(_ menuView: UIView, positions: UIFloatMenuPresentStyle, iPad_window_width: CGFloat = 0, animation: Bool = true) { + let appRect = (UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.bounds)! + let topPadding = UIFloatMenuHelper.getPadding(.top) + let bottomPadding = UIFloatMenuHelper.getPadding(.bottom) + + var bottomSpace: CGFloat { + return bottomPadding.isZero ? 15 : 30 + } + + var getPrepare: CGFloat { + return appRect.height + bottomPadding + topPadding + menuView.bounds.height + } + + var getShow: CGFloat { + let lastHeight = menuView.bounds.height + return appRect.height-lastHeight-bottomSpace+(lastHeight/2) + } + + switch positions { + case .center: + if iPad_window_width != 0 { + menuView.center = CGPoint(x: iPad_window_width/2, y: appRect.height/2) + } else { + menuView.center = CGPoint(x: appRect.width/2, y: appRect.height/2) + } + + menuView.alpha = 0 + UIView.animate(withDuration: animationDuration, animations: { + menuView.alpha = 1 + }) + break + case .default: + if iPad_window_width != 0 { + menuView.center.x = appRect.width/2 + } else { + if animation { + menuView.center = CGPoint(x: appRect.width/2, y: getPrepare) + + UIView.animate(withDuration: animationDuration, delay: 0, options: .transitionCurlUp, animations: { + menuView.center.y = getShow + }) + } else { + menuView.center = CGPoint(x: appRect.width/2, y: getShow) + } + } + break + case .leftUp(let overNavBar): + let space: CGFloat = overNavBar ? 5 : 44 + menuView.center = CGPoint(x: (menuView.frame.size.width/2)+10, y: (menuView.frame.size.height/2)+topPadding+space) + + if animation { + menuView.alpha = 0 + UIView.animate(withDuration: animationDuration, animations: { + menuView.alpha = 1 + }) + } + break + case .leftDown(let overToolBar): + let space: CGFloat = overToolBar ? 0 : 44 + menuView.center = CGPoint(x: (menuView.frame.size.width/2)+10, y: appRect.height-(menuView.frame.size.height/2)-10-space) + + if animation { + menuView.alpha = 0 + UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseOut, animations: { + menuView.alpha = 1 + }) + } + break + case .rightUp(let overNavBar): + let space: CGFloat = overNavBar ? 5 : 44 + if iPad_window_width != 0 { + menuView.center = CGPoint(x: iPad_window_width-(menuView.frame.size.width/2)-10, y: (menuView.frame.size.height/2)+topPadding+space) + } else { + menuView.center = CGPoint(x: appRect.width-(menuView.frame.size.width/2)-10, y: (menuView.frame.size.height/2)+topPadding+space) + + if animation { + menuView.alpha = 0 + UIView.animate(withDuration: animationDuration, animations: { + menuView.alpha = 1 + }) + } + } + break + case .rightDown(let overToolBar): + if iPad_window_width != 0 { + menuView.center.x = iPad_window_width-(menuView.frame.size.width/2)-10 + } else { + let space: CGFloat = overToolBar ? 0 : 44 + menuView.center = CGPoint(x: appRect.width-(menuView.frame.size.width/2)-10, y: appRect.height-(menuView.frame.size.height/2)-10-space) + + if animation { + menuView.alpha = 0 + UIView.animate(withDuration: animationDuration, animations: { + menuView.alpha = 1 + }) + } + } + break + } + } + +} diff --git a/Sources/UIFloatMenu.xcassets/Contents.json b/Sources/UIFloatMenu.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Sources/UIFloatMenu.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/UIFloatMenu.xcassets/UIMenuMainColor.colorset/Contents.json b/Sources/UIFloatMenu.xcassets/UIMenuMainColor.colorset/Contents.json new file mode 100644 index 0000000..95eb50c --- /dev/null +++ b/Sources/UIFloatMenu.xcassets/UIMenuMainColor.colorset/Contents.json @@ -0,0 +1,56 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "extended-srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.192", + "green" : "0.173", + "red" : "0.153" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "color" : { + "color-space" : "extended-srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.976", + "green" : "0.980", + "red" : "0.980" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "extended-srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.192", + "green" : "0.173", + "red" : "0.153" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/UIFloatMenu.xcassets/UIMenuRevColor.colorset/Contents.json b/Sources/UIFloatMenu.xcassets/UIMenuRevColor.colorset/Contents.json new file mode 100644 index 0000000..5a45ba9 --- /dev/null +++ b/Sources/UIFloatMenu.xcassets/UIMenuRevColor.colorset/Contents.json @@ -0,0 +1,56 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "extended-srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.976", + "green" : "0.980", + "red" : "0.980" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "color" : { + "color-space" : "extended-srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.192", + "green" : "0.173", + "red" : "0.153" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "extended-srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.976", + "green" : "0.980", + "red" : "0.980" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/UIFloatMenuAction.swift b/Sources/UIFloatMenuAction.swift new file mode 100644 index 0000000..ed71222 --- /dev/null +++ b/Sources/UIFloatMenuAction.swift @@ -0,0 +1,145 @@ +// +// UIFloatMenuAction.swift +// UIFloatMenu +// + +import UIKit + +//MARK: - labelConfig +public enum labelConfig { + case config(fontSize: CGFloat = 15, fontWeight: UIFont.Weight = .semibold) +} + +//MARK: - itemColor +public enum itemColor { + case standard + case clear + case filled(_ color: UIColor) + case tinted(_ color: UIColor) + case custom(iconColor: UIColor = .clear, textColor: UIColor, backColor: UIColor) +} + +//MARK: - cellLayout +public enum cellLayout { + case Icon_Title + case Title_Icon +} + +//MARK: - spacerType +public enum spacerType { + case empty + case line(_ color: UIColor = UIColor.gray.withAlphaComponent(0.25), inset: CGFloat = 15) + case dashedLine(_ color: UIColor = UIColor.gray.withAlphaComponent(0.35)) + case divider +} + +//MARK: - itemSetup +public enum itemSetup { + /** + UIFloatMenu: ActionCell + + - Parameter icon: Optional + - Parameter title: Title + - Parameter subtitle: Optional + - Parameter layout: Loyout of cell (**Title_Icon** or **Icon_Title**), Default: **.Title_Icon**. + */ + case ActionCell(icon: UIImage? = nil, title: String, subtitle: String = "", layout: cellLayout = .Title_Icon) + + /** + UIFloatMenu: Title + + - Parameter title: Title + */ + case Title(_ title: String) + + /** + UIFloatMenu: Spacer + + - Parameter type: Type of spacer (**.empty**, **.line**, **.divider**) + */ + case Spacer(type: spacerType = .empty) + + /** + UIFloatMenu: InfoCell + + - Parameter icon: Optional + - Parameter title: Title + - Parameter label: Configuration of label (**fontSize**, **fontWeight**) + - Parameter type: Type of spacer (**.empty**, **.line**, **.divider**) + */ + case InfoCell(icon: UIImage? = nil, title: String, + label: labelConfig = .config(fontSize: 15, fontWeight: .semibold)) + + /** + UIFloatMenu: TextFieldCell + + - Parameter title: Data in TextField at start + - Parameter placeholder: Placeholder + - Parameter isResponder: Is active at start + - Parameter isSecure: Show dots + - Parameter content: UITextContentType + - Parameter keyboard: UIKeyboardType + */ + case TextFieldCell(title: String = "", placeholder: String, isResponder: Bool = false, + isSecure: Bool = false, content: UITextContentType? = nil, keyboard: UIKeyboardType = .default) + + /** + UIFloatMenu: SwitchCell + + - Parameter icon: Optional + - Parameter title: Title + - Parameter isOn: Is on at start + - Parameter tintColor: Tint color + - Parameter action: Action for Switch + */ + case SwitchCell(icon: UIImage? = nil, title: String, isOn: Bool = false, tintColor: UIColor = .systemBlue, action: Selector) + + /** + UIFloatMenu: SwitchCell + + - Parameter title: Optional + - Parameter items: Items **[UIImage, String]** + - Parameter selected: Selected item + - Parameter action: Action for SegmentControl + */ + case SegmentCell(title: String = "", items: [Any], selected: Int = 0, action: Selector) +} + +public typealias UIFloatMenuActionHandler = (UIFloatMenuAction) -> Void + +//MARK: - UIFloatMenuAction +public class UIFloatMenuAction { + + /** + UIFloatMenu: item + */ + public var item: itemSetup + + /** + UIFloatMenu: itemColor + */ + public var itemColor: itemColor + + /** + UIFloatMenu: closeOnTap + */ + public var closeOnTap: Bool + + /** + UIFloatMenu: action + */ + public var action: UIFloatMenuActionHandler? + + public init(item: itemSetup, + itemColor: itemColor = .standard, + closeOnTap: Bool = true, + action: UIFloatMenuActionHandler? = nil) { + self.item = item + + self.itemColor = itemColor + + self.closeOnTap = closeOnTap + self.action = action + } + +} diff --git a/Sources/UIFloatMenuConfig.swift b/Sources/UIFloatMenuConfig.swift new file mode 100644 index 0000000..1c02332 --- /dev/null +++ b/Sources/UIFloatMenuConfig.swift @@ -0,0 +1,198 @@ +// +// UIFloatMenuConfig.swift +// UIFloatMenu +// + +import UIKit + +//MARK: - presentationStyle +/** + UIFloatMenu: Present styles. + */ +public enum UIFloatMenuPresentStyle { + + /** + UIFloatMenu: Located in center of window + */ + case center + + /** + UIFloatMenu: Presented from buttom + */ + case `default` + + /** + UIFloatMenu: Located in left up corner + + overToolBar = **false** + */ + case leftUp(overNavBar: Bool = false) + + /** + UIFloatMenu: Located in left down corner + + overToolBar = **true** + */ + case leftDown(overToolBar: Bool = true) + + + /** + UIFloatMenu: Located in right up corner + + overToolBar = **false** + */ + case rightUp(overNavBar: Bool = false) + + /** + UIFloatMenu: Located in right down + + overToolBar = **true** + */ + case rightDown(overToolBar: Bool = true) +} + +// MARK: - UIFloatMenuConfig +public struct UIFloatMenuConfig { + + /** + UIFloatMenu: Corner radius of menu + + Default: **12**. + */ + public var cornerRadius: CGFloat! + + /** + UIFloatMenu: Width of menu for iPad (for iPhone is always **window.width-30**) + + Default: **400**. + */ + public var viewWidth_iPad: CGFloat! + + /** + UIFloatMenu: Add blur to background + + Default: **false**. + */ + public var blurBackground: Bool + + /** + UIFloatMenu: Presentation styles + + - Parameter center: Located in center of window + - Parameter default: Presented from bottom + - Parameter leftUp: Located in left up corner + - Parameter leftDown: Located in left down corner + - Parameter rightUp: Located in right up corner + - Parameter rightDown: Located in right down corner + + Default: **.default**. + */ + public var presentation: UIFloatMenuPresentStyle! + + public init(cornerRadius: CGFloat = 12, + blurBackground: Bool = false, + presentation: UIFloatMenuPresentStyle = .default, + viewWidth_iPad: CGFloat = 400) { + self.cornerRadius = cornerRadius + self.blurBackground = blurBackground + self.presentation = presentation + + self.viewWidth_iPad = viewWidth_iPad + } + +} + +// MARK: - UIMenuHeaderConfig +public struct UIFloatMenuHeaderConfig { + + /** + UIFloatMenu: Show header + + Default: **true**. + */ + public var showHeader: Bool + public var image: UIImage? + + /** + UIFloatMenu: Title of header + */ + public var title: String + + /** + UIFloatMenu: Subtitle of header (optional) + */ + public var subTitle: String? + + /** + UIFloatMenu: Show line under header + + Default: **true**. + */ + public var showLine: Bool + + /** + UIFloatMenu: **Left** and **Right** insets for line + + Default: **15**. + */ + public var lineInset: CGFloat + + public init(showHeader: Bool = true, image: UIImage? = nil, title: String = "", subTitle: String? = "", + showLine: Bool = true, lineInset: CGFloat = 15) { + self.showHeader = showHeader + self.image = image + + self.title = title + self.subTitle = subTitle + + self.showLine = showLine + self.lineInset = lineInset + } + +} + +// MARK: - UIFloatMenuQueue +public struct UIFloatMenuQueue { + + public var uuid: Int! + public var viewHeight: CGFloat! + public var config: UIFloatMenuConfig! + public var actions: [UIFloatMenuAction]! + + public init(uuid: Int, viewHeight: CGFloat = 0, config: UIFloatMenuConfig, actions: [UIFloatMenuAction]) { + self.uuid = uuid + self.viewHeight = viewHeight + self.config = config + self.actions = actions + } + +} + +// MARK: - UIFloatMenuID +class UIFloatMenuID { + + let shared = UIFloatMenuID() + + static let backViewID = 100010001 + static let containerViewID = 200020002 + static let hideViewID = 300030003 + static let IDArray = [backViewID, containerViewID, hideViewID] + + static func genUUID(_ count: Int) -> Int { + if count == 0 { + return backViewID + } else { + return backViewID+count + } + } + +} + +class UIFloatMenuColors { + + let shared = UIFloatMenuColors() + + static let mainColor = UIColor(named: "UIMenuMainColor") + static let revColor = UIColor(named: "UIMenuRevColor") + +} diff --git a/Sources/UIFloatMenuController.swift b/Sources/UIFloatMenuController.swift new file mode 100644 index 0000000..dc68ea3 --- /dev/null +++ b/Sources/UIFloatMenuController.swift @@ -0,0 +1,243 @@ +// +// UIFloatMenuController.swift +// UIFloatMenu +// + +import UIKit +import Foundation + +class UIFloatMenuController: UIViewController, UIGestureRecognizerDelegate { + + fileprivate var currentVC = UIViewController() + + private var queue = [UIFloatMenuQueue]() + + public var header = UIFloatMenuHeaderConfig() + public var config = UIFloatMenuConfig() + public var actions = [UIFloatMenuAction]() + + var closeDelegate: UIFloatMenuCloseDelegate? + var textFieldDelegate: UIFloatMenuTextFieldDelegate? + + lazy private var backgroundView = UIView() + + public var isHomeIndicatorVisible = true { + didSet { + setNeedsUpdateOfHomeIndicatorAutoHidden() + } + } + + //MARK: - init + public init() { + super.init(nibName: nil, bundle: nil) + + modalPresentationStyle = .overFullScreen + modalTransitionStyle = .crossDissolve + } + + //MARK: - coder + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - deinit + deinit { + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "UIMenuClose"), object: nil) + } + + // MARK: - viewDidLoad + override func viewDidLoad() { + super.viewDidLoad() + view.addSubview(backgroundView) + backgroundView.frame = view.frame + backgroundView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + let tap = UITapGestureRecognizer(target: self, action: #selector(tapClose)) + tap.delegate = self + tap.cancelsTouchesInView = false + backgroundView.addGestureRecognizer(tap) + + NotificationCenter.default.addObserver(self, selector: #selector(tapClose), name: NSNotification.Name(rawValue: "UIMenuClose"), object: nil) + } + + // MARK: - viewDidAppear + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + UIView.animate(withDuration: 0.25) { + if #available(iOS 13.0, *) { + let userInterfaceStyle = self.traitCollection.userInterfaceStyle + if userInterfaceStyle == .dark { + self.backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.02) + } else { + self.backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.3) + } + } else { + self.backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.3) + } + } + + queue.append(.init(uuid: UIFloatMenuID.backViewID, config: config, actions: actions)) + + let menu = UIFloatMenu.self + + menu.currentVC = currentVC + menu.headerConfig = header + menu.viewConfig = config + menu.closeDelegate = closeDelegate + menu.textFieldDelegate = textFieldDelegate + menu.show(self, actions: actions) + } + + // MARK: - prefersHomeIndicatorAutoHidden + open override var prefersHomeIndicatorAutoHidden: Bool { + return !isHomeIndicatorVisible + } + + // MARK: - show + func show(_ vc: UIViewController) { + currentVC = vc + vc.present(self, animated: false) + } + + // MARK: - tapClose + @objc private func tapClose() { + view.endEditing(true) + UIView.animate(withDuration: 0.2) { + self.backgroundView.backgroundColor = .clear + } + + UIFloatMenu.closeMenu(completion: { + self.dismiss(animated: false, completion: nil) + }) + + if closeDelegate != nil { + closeDelegate?.didCloseMenu() + } + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } + + // MARK: - detect theme changes and windows resize + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + if #available(iOS 13.0, *) { + let userInterfaceStyle = traitCollection.userInterfaceStyle + if userInterfaceStyle == .dark { + backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.02) + } else { + backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.3) + } + } else { + backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.2) + } + + if traitCollection.horizontalSizeClass == .compact { + let device = UIDevice.current.userInterfaceIdiom + let menu = UIFloatMenu.self + + if let menuView = self.view.viewWithTag(UIFloatMenuID.backViewID) { + if device == .pad { + let last = (queue.last?.config.presentation)! + if case .rightDown(_) = last { + menu.showTo(menuView, positions: .default) + } else if case .rightUp(_) = last { + menu.showTo(menuView, positions: .default, animation: false) + } else if case .leftUp(_) = last { + menu.showTo(menuView, positions: .default) + } else if case .leftDown(_) = last { + menu.showTo(menuView, positions: .default) + } else if case .default = last { + menu.showTo(menuView, positions: .default, animation: false) + } else { + menu.showTo(menuView, positions: last, iPad_window_width: 0) + } + } else { + let last = (queue.last?.config.presentation)! + if case .rightDown(_) = last { + menu.showTo(menuView, positions: .default) + } else if case .rightUp(_) = last { + menu.showTo(menuView, positions: .default) + } else if case .leftUp(_) = last { + menu.showTo(menuView, positions: .default) + } else if case .leftDown(_) = last { + menu.showTo(menuView, positions: .default) + } else if case .default = last { + menu.showTo(menuView, positions: .default) + } else { + menu.showTo(menuView, positions: last, iPad_window_width: 0) + } + } + } + } else if traitCollection.horizontalSizeClass == .regular { + print("horizontalSizeClass-regular") + } else { + print("horizontalSizeClass-unspecified") + } + } + + // MARK: - detect device rotations + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + UIView.animate(withDuration: 0.01) { + self.backgroundView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + } + + let device = UIDevice.current.userInterfaceIdiom + let menu = UIFloatMenu.self + coordinator.animate(alongsideTransition: { (context) in + if let menuView = self.view.viewWithTag(UIFloatMenuID.backViewID) { + if device == .pad { + let layout = Layout.determineLayout() + + let last = (self.queue.last?.config.presentation)! + let position: UIFloatMenuPresentStyle! + + if case .default = last { + position = .default + for gesture in menuView.gestureRecognizers! { + gesture.isEnabled = true + } + } else if case .center = last { + position = .center + for gesture in menuView.gestureRecognizers! { + gesture.isEnabled = false + } + } else { + if layout == .iPadOneThirdScreen { + position = .default + for gesture in menuView.gestureRecognizers! { + gesture.isEnabled = true + } + } else if layout == .iPadHalfScreen{ + position = last + for gesture in menuView.gestureRecognizers! { + gesture.isEnabled = false + } + } else if layout == .iPadTwoThirdScreen { + position = last + for gesture in menuView.gestureRecognizers! { + gesture.isEnabled = false + } + } else { + position = last + for gesture in menuView.gestureRecognizers! { + gesture.isEnabled = false + } + } + } + menu.showTo(menuView, positions: position, iPad_window_width: 0, animation: false) + } else if device == .phone { + if Orientation.isLandscape { + menuView.center = self.view.center + } else { + menu.showTo(menuView, positions: menu.correctPosition((self.queue.last?.config.presentation)!)) + } + } + } + }) + } + +} diff --git a/Sources/UIFloatMenuDelegate.swift b/Sources/UIFloatMenuDelegate.swift new file mode 100644 index 0000000..446e243 --- /dev/null +++ b/Sources/UIFloatMenuDelegate.swift @@ -0,0 +1,28 @@ +// +// UIFloatMenuDelegate.swift +// UIFloatMenu +// + +import UIKit + +//MARK: - UIFloatMenuCloseDelegate +public protocol UIFloatMenuCloseDelegate: AnyObject { + + /** + UIFloatMenu: Called when pressed close button (or overlay) or swiped. + */ + func didCloseMenu() + +} + +//MARK: - UIFloatMenuTextFieldDelegate +public protocol UIFloatMenuTextFieldDelegate: AnyObject { + + /** + UIFloatMenu: Called when pressed action cell. + + - Returns: Text from all textfields in menu + */ + func getTextFieldData(_ data: [String]) + +} diff --git a/Sources/UIFloatMenuHelper.swift b/Sources/UIFloatMenuHelper.swift new file mode 100644 index 0000000..a204c46 --- /dev/null +++ b/Sources/UIFloatMenuHelper.swift @@ -0,0 +1,330 @@ +// +// UIFloatMenuHelper.swift +// UIFloatMenu +// + +import UIKit +import Foundation + +class UIFloatMenuHelper { + + let shared = UIFloatMenuHelper() + + //MARK: - roundedFont() + static func roundedFont(fontSize: CGFloat, weight: UIFont.Weight) -> UIFont { + let systemFont = UIFont.systemFont(ofSize: fontSize, weight: weight) + if #available(iOS 13.0, *) { + if let descriptor = systemFont.fontDescriptor.withDesign(.rounded) { + return UIFont(descriptor: descriptor, size: fontSize) + } else { + return systemFont + } + } else { + return systemFont + } + } + + //MARK: - detectTheme() + static func detectTheme(dark: UIColor, light: UIColor, any: UIColor) -> UIColor { + if #available(iOS 13.0, *) { + let userInterfaceStyle = UIScreen.main.traitCollection.userInterfaceStyle + if userInterfaceStyle == .dark { + return dark + } else { + return light + } + } else { + return any + } + } + + //MARK: - HexToUIColor(_ hex: String) + static func HexToUIColor(_ hex: String) -> UIColor { + var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + + if (cString.hasPrefix("#")) { + cString.remove(at: cString.startIndex) + } + + if ((cString.count) != 6) { + return UIColor.gray + } + + var rgbValue:UInt64 = 0 + Scanner(string: cString).scanHexInt64(&rgbValue) + + return UIColor( + red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, + green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(rgbValue & 0x0000FF) / 255.0, + alpha: CGFloat(1.0) + ) + } + + //MARK: - padding + public enum padding { + case top + case bottom + } + + //MARK: - getPadding(_ padding: padding) + static func getPadding(_ padding: padding) -> CGFloat { + if #available(iOS 15, *) { + let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene + switch padding { + case .top: + return (windowScene?.keyWindow?.safeAreaInsets.top)! + case .bottom: + return (windowScene?.keyWindow?.safeAreaInsets.bottom)! + } + } else { + let window = UIApplication.shared.keyWindow + switch padding { + case .top: + return window?.safeAreaInsets.top ?? 0 + case .bottom: + return window?.safeAreaInsets.bottom ?? 0 + } + } + } + + //MARK: - dashedLine() + static func dashedLine(width: CGFloat, color: UIColor) -> UIView { + let backView = UIView() + backView.frame.size.width = width + backView.frame.size.height = 1 + + let shapeLayer = CAShapeLayer() + shapeLayer.strokeColor = color.cgColor + shapeLayer.lineWidth = 1.1 + shapeLayer.lineDashPattern = [2,3] + shapeLayer.lineJoin = .round + + let path = CGMutablePath() + path.addLines(between: [CGPoint(x: 0, y: 0), CGPoint(x: width, y: 0)]) + shapeLayer.path = path + backView.layer.addSublayer(shapeLayer) + + return backView + } + +} + +// MARK: - Detect gesture direction +extension UIPanGestureRecognizer { + + public struct PanGestureDirection: OptionSet { + public let rawValue: UInt8 + + public init(rawValue: UInt8) { + self.rawValue = rawValue + } + + static let Up = PanGestureDirection(rawValue: 1 << 0) + static let Down = PanGestureDirection(rawValue: 1 << 1) + } + + private func getDirectionBy(velocity: CGFloat, greater: PanGestureDirection, lower: PanGestureDirection) -> PanGestureDirection { + if velocity == 0 { + return [] + } + return velocity > 0 ? greater : lower + } + + public func direction(in view: UIView) -> PanGestureDirection { + let velocity = self.velocity(in: view) + let yDirection = getDirectionBy(velocity: velocity.y, greater: PanGestureDirection.Down, lower: PanGestureDirection.Up) + return (yDirection) + } +} + +extension UIView { + + //MARK: - UIControlViewShadow() + func UIMenuShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float) { + layer.masksToBounds = false + layer.shadowOffset = offset + layer.shadowColor = color.cgColor + layer.shadowRadius = radius + layer.shadowOpacity = opacity + } + + //MARK: - UIControlViewRoundCorners() + func UIMenuRoundCorners(_ corners: UIRectCorner, radius: CGFloat) { + if #available(iOS 11.0, *) { + clipsToBounds = true + layer.cornerRadius = radius + layer.maskedCorners = CACornerMask(rawValue: corners.rawValue) + } else { + let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, + cornerRadii: CGSize(width: radius, height: radius)) + let mask = CAShapeLayer() + mask.path = path.cgPath + layer.mask = mask + } + } + +} + +extension UIColor { + + //MARK: - isLight + var isLight: Bool { + var white: CGFloat = 0 + getWhite(&white, alpha: nil) + return white > 0.8 + } + + //MARK: - lighter/darker + func lighter(by percentage: CGFloat = 30.0) -> UIColor? { + return self.adjust(by: abs(percentage) ) + } + + func darker(by percentage: CGFloat = 30.0) -> UIColor? { + return self.adjust(by: -1 * abs(percentage) ) + } + + func adjust(by percentage: CGFloat = 30.0) -> UIColor? { + var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 + if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) { + return UIColor(red: min(red + percentage/100, 1.0), + green: min(green + percentage/100, 1.0), + blue: min(blue + percentage/100, 1.0), + alpha: alpha) + } else { + return nil + } + } + +} + +extension UITableView { + var contentSizeHeight: CGFloat { + var height = CGFloat(0) + for section in 0.. LayoutStyle { + if UIDevice.current.userInterfaceIdiom == .phone { + return .iPhoneFullScreen + } + + let screenSize = UIScreen.main.bounds.size + let appSize = UIApplication.shared.windows[0].bounds.size + let screenWidth = screenSize.width + let appWidth = appSize.width + + if screenSize == appSize { + return .iPadFullscreen // Full screen + } + + let persent = CGFloat(appWidth / screenWidth) * 100.0 + + if persent <= 55.0 && persent >= 45.0 { + return .iPadHalfScreen // The view persent between 45-55 - it's half screen + } else if persent > 55.0 { + return .iPadTwoThirdScreen // More than 55% - it's 2/3 + } else { + return .iPadOneThirdScreen // Less than 45% - it's 1/3 + } + } + +} + +// MARK: - Orientation +struct Orientation { + + static var isLandscape: Bool { + get { + return UIDevice.current.orientation.isValidInterfaceOrientation + ? UIDevice.current.orientation.isLandscape + : (UIApplication.shared.windows.first?.windowScene?.interfaceOrientation.isLandscape)! + } + } + + static var isPortrait: Bool { + get { + return UIDevice.current.orientation.isValidInterfaceOrientation + ? UIDevice.current.orientation.isPortrait + : (UIApplication.shared.windows.first?.windowScene?.interfaceOrientation.isPortrait)! + } + } +} + +//MARK: - KeyboardHelper +final class KeyboardHelper { + + enum Animation { + case keyboardWillShow + case keyboardWillHide + } + + typealias HandleBlock = (_ animation: Animation, _ keyboardFrame: CGRect, _ duration: TimeInterval) -> Void + + private let handleBlock: HandleBlock + + init(handleBlock: @escaping HandleBlock) { + self.handleBlock = handleBlock + setupNotification() + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + private func setupNotification() { + _ = NotificationCenter.default + .addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { [weak self] notification in + self?.handle(animation: .keyboardWillShow, notification: notification) + } + + _ = NotificationCenter.default + .addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { [weak self] notification in + self?.handle(animation: .keyboardWillHide, notification: notification) + } + } + + private func handle(animation: Animation, notification: Notification) { + guard let userInfo = notification.userInfo, + let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, + let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double + else { return } + + handleBlock(animation, keyboardFrame, duration) + } +} diff --git a/Sources/UIFloatMenuView/Cell/UIFloatMenuActionCell.swift b/Sources/UIFloatMenuView/Cell/UIFloatMenuActionCell.swift new file mode 100644 index 0000000..ab67797 --- /dev/null +++ b/Sources/UIFloatMenuView/Cell/UIFloatMenuActionCell.swift @@ -0,0 +1,216 @@ +// +// UIFloatMenuActionCell.swift +// UIFloatMenu +// + +import UIKit + +class UIFloatMenuActionCell: UITableViewCell { + + var itemColor: itemColor! + var itemLayout: cellLayout! + + var initBackColor: UIColor! + + // MARK: Views + private var contentStackView: UIStackView = UIStackView() + + var backView: UIView = { + let uiview = UIView() + uiview.layer.cornerRadius = 8 + uiview.isUserInteractionEnabled = false + return uiview + }() + + var titleLabel: UILabel = { + let label = UILabel() + label.textColor = UIFloatMenuColors.revColor + label.numberOfLines = 1 + label.isUserInteractionEnabled = false + label.font = UIFloatMenuHelper.roundedFont(fontSize: 16, weight: .semibold) + return label + }() + + var subtitleLabel: UILabel = { + let label = UILabel() + label.textColor = UIFloatMenuColors.revColor + label.numberOfLines = 1 + label.font = UIFloatMenuHelper.roundedFont(fontSize: 11, weight: .semibold) + label.alpha = 0.8 + label.isUserInteractionEnabled = false + return label + }() + + var iconImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + imageView.isUserInteractionEnabled = false + return imageView + }() + + // MARK: init + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + selectionStyle = .none + backgroundColor = .clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: setHighlighted + override func setHighlighted(_ highlighted: Bool, animated: Bool) { + super.setHighlighted(highlighted, animated: animated) + if highlighted { + switch itemColor { + case .clear: + let color = UIFloatMenuHelper.detectTheme(dark: UIColor.lightGray.withAlphaComponent(0.08), + light: UIColor.lightGray.withAlphaComponent(0.12), + any: UIColor.lightGray.withAlphaComponent(0.08)) + backView.backgroundColor = color + break + case .tinted(_): + break + case .filled(_): + backView.alpha = 0.9 + break + case .standard: + backView.alpha = 0.9 + break + case .custom(_, let tintColor, let backColor): + if backColor == .clear { + backView.backgroundColor = tintColor.withAlphaComponent(0.1) + } + break + case .none: + break + } + } else { + backView.backgroundColor = initBackColor + backView.alpha = 1 + } + } + + // MARK: layoutSubviews + override func layoutSubviews() { + super.layoutSubviews() + contentView.addSubview(backView) + + if iconImageView.image != nil { + backView.addSubview(iconImageView) + } + + backView.frame.size = CGSize(width: frame.width-20, height: frame.height-5) + backView.center.x = frame.width/2 + backView.center.y = frame.height/2 + + contentStackView.axis = .vertical + contentStackView.distribution = .fillProportionally + + if subtitleLabel.text != "" { + contentStackView.addArrangedSubview(titleLabel) + contentStackView.addArrangedSubview(subtitleLabel) + } else { + contentStackView.addArrangedSubview(titleLabel) + } + + addSubview(contentStackView) + + contentStackView.translatesAutoresizingMaskIntoConstraints = false + + switch itemLayout { + case .Icon_Title: + if iconImageView.image != nil { + iconImageView.frame.origin.x = 10 + } + + NSLayoutConstraint.activate([ + contentStackView.topAnchor.constraint(equalTo: backView.topAnchor, constant: 10), + contentStackView.leadingAnchor.constraint(equalTo: backView.leadingAnchor, constant: iconImageView.image != nil ? 40 : 10), + contentStackView.bottomAnchor.constraint(equalTo: backView.bottomAnchor, constant: -10), + contentStackView.trailingAnchor.constraint(equalTo: backView.trailingAnchor, constant: -10) + ]) + break + case .Title_Icon: + if iconImageView.image != nil { + iconImageView.frame.origin.x = backView.frame.width-35 + } + + NSLayoutConstraint.activate([ + contentStackView.topAnchor.constraint(equalTo: backView.topAnchor, constant: 10), + contentStackView.leadingAnchor.constraint(equalTo: backView.leadingAnchor, constant: 10), + contentStackView.bottomAnchor.constraint(equalTo: backView.bottomAnchor, constant: -10), + contentStackView.trailingAnchor.constraint(equalTo: backView.trailingAnchor, constant: iconImageView.image != nil ? -40 : -10) + ]) + break + case .none: + break + } + + contentStackView.isUserInteractionEnabled = false + + iconImageView.frame.size = CGSize(width: 23, height: 23) + iconImageView.center.y = backView.frame.height/2 + + switch itemColor { + case .clear: + if iconImageView.image != nil { + let templateImage = iconImageView.image?.withRenderingMode(.alwaysTemplate) + iconImageView.image = templateImage + iconImageView.tintColor = UIFloatMenuColors.revColor + } + break + case .tinted(let color): + if iconImageView.image != nil { + let templateImage = iconImageView.image?.withRenderingMode(.alwaysTemplate) + iconImageView.image = templateImage + iconImageView.tintColor = color + } + + titleLabel.textColor = color + subtitleLabel.textColor = color.withAlphaComponent(0.9) + + backView.backgroundColor = color.withAlphaComponent(0.1) + break + case .filled(let color): + if iconImageView.image != nil { + let templateImage = iconImageView.image?.withRenderingMode(.alwaysTemplate) + iconImageView.image = templateImage + iconImageView.tintColor = .white + } + + titleLabel.textColor = .white + subtitleLabel.textColor = UIColor.white.withAlphaComponent(0.9) + + backView.backgroundColor = color + break + case .standard: + if iconImageView.image != nil { + let templateImage = iconImageView.image?.withRenderingMode(.alwaysTemplate) + iconImageView.image = templateImage + iconImageView.tintColor = UIFloatMenuColors.revColor + } + + titleLabel.textColor = UIFloatMenuColors.revColor + subtitleLabel.textColor = UIFloatMenuColors.revColor + + backView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.08) + break + case .custom(let iconColor, let textColor, let backColor): + let image = iconColor != .clear ? iconImageView.image?.withRenderingMode(.alwaysTemplate) : iconImageView.image + iconImageView.image = image + iconImageView.tintColor = iconColor + + titleLabel.textColor = textColor + subtitleLabel.textColor = textColor.withAlphaComponent(0.9) + + if backColor != .clear { + backView.backgroundColor = backColor + } + break + case .none: + break + } + } +} diff --git a/Sources/UIFloatMenuView/Cell/UIFloatMenuInfoCell.swift b/Sources/UIFloatMenuView/Cell/UIFloatMenuInfoCell.swift new file mode 100644 index 0000000..72f8529 --- /dev/null +++ b/Sources/UIFloatMenuView/Cell/UIFloatMenuInfoCell.swift @@ -0,0 +1,69 @@ +// +// UIFloatMenuInfoCell.swift +// UIFloatMenu +// + +import UIKit + +class UIFloatMenuInfoCell: UITableViewCell { + + // MARK: Views + private var contentStackView: UIStackView = UIStackView() + + lazy var iconImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + imageView.isUserInteractionEnabled = false + return imageView + }() + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.textColor = UIFloatMenuColors.revColor + label.numberOfLines = 0 + label.isUserInteractionEnabled = false + return label + }() + + // MARK: init + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + selectionStyle = .none + backgroundColor = .clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: layoutSubviews + override func layoutSubviews() { + super.layoutSubviews() + if iconImageView.image == nil { + contentStackView.addArrangedSubview(titleLabel) + } else { + contentStackView.addArrangedSubview(iconImageView) + contentStackView.addArrangedSubview(titleLabel) + } + + contentStackView.axis = .horizontal + contentStackView.spacing = 9 + contentStackView.distribution = .fillProportionally + addSubview(contentStackView) + + NSLayoutConstraint.activate([ + iconImageView.heightAnchor.constraint(equalToConstant: 23), + iconImageView.widthAnchor.constraint(equalToConstant: 23), + titleLabel.heightAnchor.constraint(equalToConstant: 30), + contentStackView.topAnchor.constraint(equalTo: topAnchor, constant: 5), + contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 13), + contentStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -5), + contentStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -13) + ]) + contentStackView.translatesAutoresizingMaskIntoConstraints = false + + let templateImage = iconImageView.image?.withRenderingMode(.alwaysTemplate) + iconImageView.image = templateImage + iconImageView.tintColor = UIFloatMenuColors.revColor?.withAlphaComponent(0.65) + } +} diff --git a/Sources/UIFloatMenuView/Cell/UIFloatMenuSegmentCell.swift b/Sources/UIFloatMenuView/Cell/UIFloatMenuSegmentCell.swift new file mode 100644 index 0000000..dd94af1 --- /dev/null +++ b/Sources/UIFloatMenuView/Cell/UIFloatMenuSegmentCell.swift @@ -0,0 +1,68 @@ +// +// UIFloatMenuSegmentCell.swift +// UIFloatMenu +// + +import UIKit + +class UIFloatMenuSegmentCell: UITableViewCell { + + private var contentStackView: UIStackView = UIStackView() + + var items = [Any]() + var vc: UIViewController! + var selectedItem: Int! + var action: Selector! + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.textColor = UIFloatMenuColors.revColor + label.numberOfLines = 1 + label.isUserInteractionEnabled = false + label.font = UIFloatMenuHelper.roundedFont(fontSize: 16, weight: .semibold) + return label + }() + + lazy var segmentView: UISegmentedControl = { + let segment = UISegmentedControl(items: items) + segment.selectedSegmentIndex = selectedItem + segment.addTarget(vc, action: action, for: .valueChanged) + return segment + }() + + // MARK: init + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + selectionStyle = .none + backgroundColor = .clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: layoutSubviews + override func layoutSubviews() { + super.layoutSubviews() + if titleLabel.text != "" { + contentStackView.addArrangedSubview(titleLabel) + contentStackView.spacing = 5 + } else { + contentStackView.spacing = 0 + } + contentStackView.addArrangedSubview(segmentView) + contentStackView.axis = .horizontal + contentStackView.distribution = .fillProportionally + + contentView.addSubview(contentStackView) + + NSLayoutConstraint.activate([ + contentStackView.topAnchor.constraint(equalTo: topAnchor, constant: 4), + contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12), + contentStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4), + contentStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12) + ]) + contentStackView.translatesAutoresizingMaskIntoConstraints = false + } + +} diff --git a/Sources/UIFloatMenuView/Cell/UIFloatMenuSpacerCell.swift b/Sources/UIFloatMenuView/Cell/UIFloatMenuSpacerCell.swift new file mode 100644 index 0000000..c715b82 --- /dev/null +++ b/Sources/UIFloatMenuView/Cell/UIFloatMenuSpacerCell.swift @@ -0,0 +1,97 @@ +// +// UIFloatMenuSpacerCell.swift +// UIFloatMenu +// + +import UIKit + +class UIFloatMenuSpacerCell: UITableViewCell { + + var spacerType: spacerType! + + private var contentStackView: UIStackView = UIStackView() + + // MARK: init + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + selectionStyle = .none + isUserInteractionEnabled = false + backgroundColor = .clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: layoutSubviews + override func layoutSubviews() { + super.layoutSubviews() + switch spacerType { + case .empty: + break + case .line(let color, let inset): + let lineView: UIView = { + let uiview = UIView() + uiview.backgroundColor = color + uiview.frame.size = CGSize(width: frame.width-30, height: 1) + uiview.center = CGPoint(x: frame.width/2, y: frame.height/2) + uiview.isUserInteractionEnabled = false + return uiview + }() + + contentStackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 4.5, leading: inset, bottom: 4.5, trailing: inset) + + contentStackView.addArrangedSubview(lineView) + break + case .dashedLine(let color): + let dashedView = UIFloatMenuHelper.dashedLine(width: frame.width-30, color: color) + dashedView.isUserInteractionEnabled = false + + contentStackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 4.5, leading: 15, bottom: 4.5, trailing: 15) + + contentStackView.addArrangedSubview(dashedView) + break + case .divider: + let dividerView: UIView = { + let uiview = UIView() + uiview.backgroundColor = dividerColor + uiview.frame.size = CGSize(width: frame.width, height: frame.size.height) + uiview.frame.origin = CGPoint(x: 0, y: 0) + uiview.isUserInteractionEnabled = false + return uiview + }() + + contentStackView.addArrangedSubview(dividerView) + break + case .none: + break + } + + contentStackView.isLayoutMarginsRelativeArrangement = true + contentStackView.axis = .vertical + contentStackView.distribution = .fillProportionally + addSubview(contentStackView) + + NSLayoutConstraint.activate([ + contentStackView.topAnchor.constraint(equalTo: topAnchor, constant: 0), + contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0), + contentStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -0), + contentStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -0) + ]) + contentStackView.translatesAutoresizingMaskIntoConstraints = false + } + + private var dividerColor: UIColor = { + if #available(iOS 13, *) { + return UIColor { (UITraitCollection: UITraitCollection) -> UIColor in + if UITraitCollection.userInterfaceStyle == .dark { + return UIColor.darkGray.withAlphaComponent(0.2) + } else { + return UIColor.lightGray.withAlphaComponent(0.15) + } + } + } else { + return UIColor.darkGray.withAlphaComponent(0.15) + } + }() +} diff --git a/Sources/UIFloatMenuView/Cell/UIFloatMenuSwitchCell.swift b/Sources/UIFloatMenuView/Cell/UIFloatMenuSwitchCell.swift new file mode 100644 index 0000000..3e11202 --- /dev/null +++ b/Sources/UIFloatMenuView/Cell/UIFloatMenuSwitchCell.swift @@ -0,0 +1,81 @@ +// +// UIFloatMenuSwitchCell.swift +// UIFloatMenu +// + +import UIKit + +class UIFloatMenuSwitchCell: UITableViewCell { + + private var contentStackView: UIStackView = UIStackView() + + // MARK: Views + lazy var titleLabel: UILabel = { + let label = UILabel() + label.textColor = UIFloatMenuColors.revColor + label.numberOfLines = 1 + label.isUserInteractionEnabled = false + label.font = UIFloatMenuHelper.roundedFont(fontSize: 16, weight: .semibold) + return label + }() + + lazy var iconImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + imageView.isUserInteractionEnabled = false + return imageView + }() + + lazy var switchView: UISwitch = { + let sw = UISwitch() + return sw + }() + + // MARK: init + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + selectionStyle = .none + backgroundColor = .clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: layoutSubviews + override func layoutSubviews() { + super.layoutSubviews() + contentView.addSubview(switchView) + + if iconImageView.image == nil { + contentStackView.addArrangedSubview(titleLabel) + } else { + contentStackView.addArrangedSubview(iconImageView) + contentStackView.addArrangedSubview(titleLabel) + } + + contentStackView.isLayoutMarginsRelativeArrangement = true + contentStackView.axis = .horizontal + contentStackView.spacing = 9 + contentStackView.distribution = .fillProportionally + addSubview(contentStackView) + + NSLayoutConstraint.activate([ + iconImageView.heightAnchor.constraint(equalToConstant: 23), + iconImageView.widthAnchor.constraint(equalToConstant: 23), + titleLabel.heightAnchor.constraint(equalToConstant: 30), + contentStackView.topAnchor.constraint(equalTo: topAnchor, constant: 7), + contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 13), + contentStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -7), + contentStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -70) + ]) + contentStackView.translatesAutoresizingMaskIntoConstraints = false + + switchView.center.x = frame.width-38 + switchView.center.y = frame.height/2 + + let templateImage = iconImageView.image?.withRenderingMode(.alwaysTemplate) + iconImageView.image = templateImage + iconImageView.tintColor = UIFloatMenuColors.revColor?.withAlphaComponent(0.65) + } +} diff --git a/Sources/UIFloatMenuView/Cell/UIFloatMenuTextFieldCell.swift b/Sources/UIFloatMenuView/Cell/UIFloatMenuTextFieldCell.swift new file mode 100644 index 0000000..1c990b1 --- /dev/null +++ b/Sources/UIFloatMenuView/Cell/UIFloatMenuTextFieldCell.swift @@ -0,0 +1,51 @@ +// +// UIFloatMenuTextFieldCell.swift +// UIFloatMenu +// + +import UIKit + +class UIFloatMenuTextFieldCell: UITableViewCell { + + // MARK: Views + lazy var backView: UIView = { + let uiview = UIView() + uiview.layer.cornerRadius = 8 + uiview.backgroundColor = UIColor.lightGray.withAlphaComponent(0.08) + uiview.layer.borderWidth = 0.5 + uiview.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.25).cgColor + return uiview + }() + + lazy var TextField: UITextField = { + let textF = UITextField() + textF.setLeftPadding(12) + textF.setRightPadding(12) + textF.font = UIFloatMenuHelper.roundedFont(fontSize: 17, weight: .regular) + return textF + }() + + // MARK: init + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + selectionStyle = .none + backgroundColor = .clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: layoutSubviews + override func layoutSubviews() { + super.layoutSubviews() + contentView.addSubview(backView) + + backView.addSubview(TextField) + backView.frame.size = CGSize(width: frame.width-20, height: frame.height-5) + backView.center.x = frame.width/2 + backView.center.y = frame.height/2 + + TextField.frame = CGRect(x: 0, y: 0, width: backView.frame.size.width, height: backView.frame.size.height) + } +} diff --git a/Sources/UIFloatMenuView/Cell/UIFloatMenuTitleCell.swift b/Sources/UIFloatMenuView/Cell/UIFloatMenuTitleCell.swift new file mode 100644 index 0000000..8e7e679 --- /dev/null +++ b/Sources/UIFloatMenuView/Cell/UIFloatMenuTitleCell.swift @@ -0,0 +1,41 @@ +// +// UIFloatMenuTitleCell.swift +// UIFloatMenu +// + +import UIKit + +class UIFloatMenuTitleCell: UITableViewCell { + + // MARK: Views + lazy var titleLabel: UILabel = { + let label = UILabel() + label.textColor = UIFloatMenuColors.revColor + label.numberOfLines = 1 + label.isUserInteractionEnabled = false + label.font = UIFloatMenuHelper.roundedFont(fontSize: 16, weight: .semibold) + return label + }() + + // MARK: init + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + selectionStyle = .none + isUserInteractionEnabled = false + backgroundColor = .clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: layoutSubviews + override func layoutSubviews() { + super.layoutSubviews() + contentView.addSubview(titleLabel) + + titleLabel.frame.size = CGSize(width: frame.width-50, height: 20) + titleLabel.center.y = frame.height/2 + titleLabel.frame.origin.x = 10 + } +} diff --git a/Sources/UIFloatMenuView/UIFloatMenuHeaderView.swift b/Sources/UIFloatMenuView/UIFloatMenuHeaderView.swift new file mode 100644 index 0000000..44f18d1 --- /dev/null +++ b/Sources/UIFloatMenuView/UIFloatMenuHeaderView.swift @@ -0,0 +1,128 @@ +// +// UIFloatMenuHeaderView.swift +// UIFloatMenu +// + +import UIKit +import Foundation + +class UIFloatMenuHeaderView: UIView { + + private var stackView: UIStackView = UIStackView() + + var menuConfig = UIFloatMenuConfig() + var headerConfig = UIFloatMenuHeaderConfig() + + private var viewWidth = CGFloat() + + private var headerHeight: CGFloat = 60 + + //MARK: - Views + private let TitleLabel: UILabel = { + let label = UILabel() + label.textAlignment = .left + label.textColor = UIFloatMenuColors.revColor + label.font = UIFloatMenuHelper.roundedFont(fontSize: 18, weight: .semibold) + return label + }() + + private let SubtitleLabel: UILabel = { + let label = UILabel() + label.textAlignment = .left + label.textColor = UIFloatMenuColors.revColor + label.font = UIFloatMenuHelper.roundedFont(fontSize: 11, weight: .regular) + return label + }() + + private var cancelButton: UIButton = { + let button = UIButton(type: .system) + button.isEnabled = true + button.setImage(UIImage(systemName: "multiply.circle.fill", withConfiguration: UIImage.SymbolConfiguration(textStyle: .title3)), for: .normal) + button.tintColor = UIColor.systemGray.withAlphaComponent(0.4) + return button + }() + + lazy var lineView: UIView = { + let uiview = UIView() + uiview.backgroundColor = UIColor.gray.withAlphaComponent(0.25) + uiview.frame.origin.y = headerHeight-1 + return uiview + }() + + //MARK: - init + public init(headerConfig: UIFloatMenuHeaderConfig, menuConfig: UIFloatMenuConfig) { + super.init(frame: CGRect.zero) + let appRect = UIApplication.shared.windows[0].bounds + let device = UIDevice.current.userInterfaceIdiom + let width = (device == .pad ? menuConfig.viewWidth_iPad : (Orientation.isPortrait ? appRect.width-30 : appRect.width/2.5))! + + self.headerConfig = headerConfig + self.menuConfig = menuConfig + self.viewWidth = width + + addSubview(cancelButton) + backgroundColor = menuConfig.blurBackground ? .clear : UIFloatMenuColors.mainColor + + TitleLabel.text = headerConfig.title + SubtitleLabel.text = headerConfig.subTitle + + if headerConfig.showLine { + lineView.frame.size = CGSize(width: width-(headerConfig.lineInset*2), height: 1) + lineView.center.x = viewWidth/2 + addSubview(lineView) + } + + stackView.axis = .vertical + stackView.distribution = .fillProportionally + + if headerConfig.title != "" && headerConfig.subTitle != "" { + stackView.addArrangedSubview(TitleLabel) + stackView.addArrangedSubview(SubtitleLabel) + } + + if headerConfig.title != "" && headerConfig.subTitle == "" { + stackView.addArrangedSubview(TitleLabel) + } + + addSubview(stackView) + + stackView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: topAnchor, constant: 12), + stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 15), + stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12), + stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -55), + ]) + + stackView.isUserInteractionEnabled = true + + let pan = UITapGestureRecognizer(target: self, action: #selector(tapClose(_:))) + cancelButton.addGestureRecognizer(pan) + } + + //MARK: - coder + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //MARK: - layoutSubviews + override func layoutSubviews() { + super.layoutSubviews() + cancelButton.frame = CGRect(x: frame.width-41, y: 0, width: 30, height: 30) + cancelButton.center.y = headerHeight/2 + + if headerConfig.showLine { + lineView.frame.size = CGSize(width: frame.size.width-(headerConfig.lineInset*2), height: 1) + lineView.center.x = frame.size.width/2 + addSubview(lineView) + } + + frame.size.height = headerHeight + } + + // MARK: tapClose + @objc func tapClose(_ sender: UIPanGestureRecognizer) { + NotificationCenter.default.post(name: NSNotification.Name("UIMenuClose"), object: nil) + } +} diff --git a/Sources/UIFloatMenuView/UIFloatMenuView.swift b/Sources/UIFloatMenuView/UIFloatMenuView.swift new file mode 100644 index 0000000..31885f8 --- /dev/null +++ b/Sources/UIFloatMenuView/UIFloatMenuView.swift @@ -0,0 +1,322 @@ +// +// UIFloatMenuView.swift +// UIFloatMenu +// + +import UIKit +import Foundation +import SwiftUI + +class UIFloatMenuView: UIView, UITableViewDelegate, UITableViewDataSource { + + private var currentVC = UIViewController() + + private var config = UIFloatMenuConfig() + private var headerConfig = UIFloatMenuHeaderConfig() + + private var itemsData = [UIFloatMenuAction]() + + private let space: CGFloat = 8 + + var closeDelegate: UIFloatMenuCloseDelegate? + var textFieldDelegate: UIFloatMenuTextFieldDelegate? + + //MARK: - tableView + lazy var tableView : UITableView = { + let table = UITableView() + table.tag = UIFloatMenuID.backViewID + table.translatesAutoresizingMaskIntoConstraints = false + table.backgroundColor = .clear + table.isScrollEnabled = false + table.clipsToBounds = true + table.showsHorizontalScrollIndicator = false + table.showsVerticalScrollIndicator = false + table.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + table.separatorStyle = .none + table.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: space)) + table.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: space)) + return table + }() + + //MARK: - Lifecycle + public init(items: [UIFloatMenuAction], vc: UIViewController, header: UIFloatMenuHeaderConfig, config: UIFloatMenuConfig, + closeDelegate: UIFloatMenuCloseDelegate?, textFieldDelegate: UIFloatMenuTextFieldDelegate?) { + super.init(frame: CGRect.zero) + + self.currentVC = vc + self.itemsData = items + self.config = config + self.headerConfig = header + self.closeDelegate = closeDelegate + self.textFieldDelegate = textFieldDelegate + + backgroundColor = .clear + + setupTableView() + + addSubview(tableView) + + var tableH: CGFloat { + let height = tableView.contentSizeHeight+(space*2)+(headerConfig.showHeader == true ? 60 : 0) + return setMaxHeight(height) + } + + let tableW: CGFloat = setMaxWidth(tableView.bounds.width) + + let device = UIDevice.current.userInterfaceIdiom + let width = (device == .pad ? config.viewWidth_iPad : tableW)! + + if headerConfig.showHeader { + let headerView = UIFloatMenuHeaderView.init(headerConfig: header, menuConfig: config) + + addSubview(headerView) + headerView.frame.origin = CGPoint(x: 0, y: 0) + headerView.frame.size = CGSize(width: width, height: 60) + + tableView.frame = CGRect(x: 0, y: 60, width: width, height: tableH-60) + frame.size = CGSize(width: width, height: tableH) + headerView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + } else { + tableView.frame = CGRect(x: 0, y: 0, width: width, height: tableH) + frame.size = CGSize(width: width, height: tableH) + } + + if config.blurBackground { + let blurEffect = UIBlurEffect(style: .prominent) + let blurView = UIVisualEffectView(effect: blurEffect) + blurView.frame = frame + blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + addSubview(blurView) + sendSubviewToBack(blurView) + } else { + backgroundColor = UIFloatMenuColors.mainColor + } + + layer.cornerRadius = config.cornerRadius + layer.masksToBounds = true + layer.borderWidth = 0.5 + layer.borderColor = UIColor.darkGray.withAlphaComponent(0.25).cgColor + } + + //MARK: - coder + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //MARK: - layoutSubviews + override func layoutSubviews() { + super.layoutSubviews() + var tableH: CGFloat { + let height = tableView.contentSizeHeight+(space*2)+(headerConfig.showHeader == true ? 60 : 0) + return setMaxHeight(height) + } + + let tableW: CGFloat = setMaxWidth(tableView.bounds.width) + + let device = UIDevice.current.userInterfaceIdiom + let width = (device == .pad ? config.viewWidth_iPad : tableW)! + + if headerConfig.showHeader { + tableView.frame = CGRect(x: 0, y: 60, width: width, height: tableH-60) + frame.size = CGSize(width: width, height: tableH) + } else { + tableView.frame = CGRect(x: 0, y: 0, width: width, height: tableH) + frame.size = CGSize(width: width, height: tableH) + } + } + + //MARK: - setupTableView + internal func setupTableView() { + tableView.register(UIFloatMenuActionCell.self, forCellReuseIdentifier: "UIFloatMenuActionCell") + tableView.register(UIFloatMenuInfoCell.self, forCellReuseIdentifier: "UIFloatMenuInfoCell") + tableView.register(UIFloatMenuTitleCell.self, forCellReuseIdentifier: "UIFloatMenuTitleCell") + tableView.register(UIFloatMenuSpacerCell.self, forCellReuseIdentifier: "UIFloatMenuSpacerCell") + tableView.register(UIFloatMenuTextFieldCell.self, forCellReuseIdentifier: "UIFloatMenuTextFieldCell") + tableView.register(UIFloatMenuSwitchCell.self, forCellReuseIdentifier: "UIFloatMenuSwitchCell") + tableView.register(UIFloatMenuSegmentCell.self, forCellReuseIdentifier: "UIFloatMenuSegmentCell") + tableView.delegate = self + tableView.dataSource = self + } + + //MARK: - numberOfRowsInSection + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return itemsData.count + } + + //MARK: - cellForRowAt + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let row = itemsData[indexPath.item] + + switch row.item { + case .ActionCell(let icon, let title, let subtitle, let layout): + let cell = tableView.dequeueReusableCell(withIdentifier: "UIFloatMenuActionCell", for: indexPath) as! UIFloatMenuActionCell + + cell.itemColor = row.itemColor + cell.itemLayout = layout + + cell.iconImageView.image = icon + + cell.titleLabel.text = title + cell.subtitleLabel.text = subtitle + + return cell + case .InfoCell(let icon, let title, let labelConfig): + let cell = tableView.dequeueReusableCell(withIdentifier: "UIFloatMenuInfoCell", for: indexPath) as! UIFloatMenuInfoCell + + cell.iconImageView.image = icon + cell.titleLabel.text = title + if case .config(let fontSize, let fontWeight) = labelConfig { + cell.titleLabel.font = UIFloatMenuHelper.roundedFont(fontSize: fontSize, weight: fontWeight) + } + + return cell + case .Title(let title): + let cell = tableView.dequeueReusableCell(withIdentifier: "UIFloatMenuTitleCell", for: indexPath) as! UIFloatMenuTitleCell + cell.titleLabel.text = title + + return cell + case .Spacer(let type): + let cell = tableView.dequeueReusableCell(withIdentifier: "UIFloatMenuSpacerCell", for: indexPath) as! UIFloatMenuSpacerCell + cell.spacerType = type + + return cell + case .TextFieldCell(let title, let placeholder, let isResponder, let isSecure, let content, let keyboard): + let cell = tableView.dequeueReusableCell(withIdentifier: "UIFloatMenuTextFieldCell", for: indexPath) as! UIFloatMenuTextFieldCell + cell.TextField.text = title + cell.TextField.placeholder = placeholder + + cell.TextField.isSecureTextEntry = isSecure + cell.TextField.textContentType = content + cell.TextField.keyboardType = keyboard + + if isResponder { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + cell.TextField.becomeFirstResponder() + } + } + + return cell + case .SwitchCell(let icon, let title, let isOn, let tintColor, let action): + let cell = tableView.dequeueReusableCell(withIdentifier: "UIFloatMenuSwitchCell", for: indexPath) as! UIFloatMenuSwitchCell + cell.iconImageView.image = icon + cell.titleLabel.text = title + + cell.switchView.onTintColor = tintColor + cell.switchView.isOn = isOn + cell.switchView.addTarget(currentVC, action: action, for: .valueChanged) + + return cell + case .SegmentCell(let title, let items, let selected, let action): + let cell = tableView.dequeueReusableCell(withIdentifier: "UIFloatMenuSegmentCell", for: indexPath) as! UIFloatMenuSegmentCell + cell.titleLabel.text = title + + cell.items = items + cell.selectedItem = selected + cell.action = action + cell.vc = currentVC + + return cell + } + } + + //MARK: - didSelectRowAt + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let row = itemsData[indexPath.item] + + if case .ActionCell = row.item { + if textFieldDelegate != nil { + textFieldDelegate?.getTextFieldData(getTF_data()) + } + + if closeDelegate != nil { + closeDelegate?.didCloseMenu() + } + + row.action!(row) + if row.closeOnTap { + NotificationCenter.default.post(name: NSNotification.Name("UIMenuClose"), object: nil) + } + } + } + + //MARK: - heightForRowAt + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + var height: CGFloat = CGFloat() + + let row = itemsData[indexPath.item] + switch row.item { + case .InfoCell(_, _, _): + height = 36 + case .Title(_): + height = 30 + case .Spacer(_): + height = 10 + case .ActionCell(_, _, _, _): + height = 57 + case .TextFieldCell(_, _, _, _, _, _): + height = 57 + case .SwitchCell(_, _, _, _, _): + height = 40 + case .SegmentCell(_, _, _, _): + height = 50 + } + + return height + } + + //MARK: - setMaxHeight() + private func setMaxHeight(_ currentHeight: CGFloat) -> CGFloat { + let topPadding = UIFloatMenuHelper.getPadding(.top) + let bottomPadding = UIFloatMenuHelper.getPadding(.bottom) + + let appRect = UIApplication.shared.windows[0].bounds + let screenH = appRect.height-topPadding-bottomPadding + var topSpace: CGFloat { + let device = UIDevice.current.userInterfaceIdiom + return device == .pad ? 70 : (Orientation.isLandscape ? 30 : 50) + } + + if currentHeight >= screenH { + tableView.isScrollEnabled = true + return appRect.height-topPadding-bottomPadding-topSpace + } + tableView.isScrollEnabled = false + return currentHeight + } + + //MARK: - setMaxWidth() + private func setMaxWidth(_ currentWidth: CGFloat) -> CGFloat { + let appRect = UIApplication.shared.windows[0].bounds + + let device = UIDevice.current.userInterfaceIdiom + + if device == .pad { + return currentWidth + } else if device == .phone { + if Orientation.isLandscape { + return appRect.width/2.5 + } else if Orientation.isPortrait { + return appRect.width-30 + } + } + return appRect.width-30 + } + + //MARK: - getTF_data() + func getTF_data() -> [String] { + var data = [String]() + + for index in 0..