From e52fefae9d86ef318b2a26705c0bb9d66c7eb8f0 Mon Sep 17 00:00:00 2001 From: Jia Chen Date: Sat, 26 Sep 2020 02:00:05 +0800 Subject: [PATCH] Added support for custom sequences and views --- .../Base.lproj/Main.storyboard | 2 +- Sources/ScrollSelection/ScrollSelection.swift | 205 ++++++++++++------ 2 files changed, 144 insertions(+), 63 deletions(-) diff --git a/Scroll Selection Demo/Scroll Selection Demo/Base.lproj/Main.storyboard b/Scroll Selection Demo/Scroll Selection Demo/Base.lproj/Main.storyboard index 763cff2..fe37039 100644 --- a/Scroll Selection Demo/Scroll Selection Demo/Base.lproj/Main.storyboard +++ b/Scroll Selection Demo/Scroll Selection Demo/Base.lproj/Main.storyboard @@ -47,7 +47,7 @@ - + diff --git a/Sources/ScrollSelection/ScrollSelection.swift b/Sources/ScrollSelection/ScrollSelection.swift index f508487..c538b64 100644 --- a/Sources/ScrollSelection/ScrollSelection.swift +++ b/Sources/ScrollSelection/ScrollSelection.swift @@ -59,6 +59,9 @@ public class ScrollSelection { /// do not get spammed with random vibrations var currentSection: Int = -1 + /// Create custom button sequence + var selectionSequence: [SelectionView] = [.rightBarButtons, .leftBarButtons] + /// Set up scroll selection /// - Parameters: /// - vc: Parent View Controller @@ -80,9 +83,8 @@ public class ScrollSelection { /// - Parameter offset: Y-axis of the scrollView's content offset /// - Returns: Current section or `nil` if nothing is selected func getCurrentSection(_ offset: CGFloat) -> Int? { - let navigationItem = parent.navigationItem - let buttons = (navigationItem.leftBarButtonItems ?? []) + (navigationItem.rightBarButtonItems ?? []) + let buttons = getViews() var currentSection = Int(floor(offset / -offsetMultiplier)) @@ -102,32 +104,64 @@ public class ScrollSelection { /// Getting the selected button based on the current section /// - Parameter section: Current selected section /// - Returns: The previous and currently selected bar buttons - func getSelectedButtons(atSection section: Int) -> (previous: [UIBarButtonItem], current: UIBarButtonItem?){ - let leftBarButtons = parent.navigationItem.leftBarButtonItems ?? [] - let rightBarButtons = parent.navigationItem.rightBarButtonItems ?? [] + func getSelectedViews(atSection section: Int) -> (previous: [UIView], current: UIView?) { - let allBarButtons = rightBarButtons + leftBarButtons.reversed() + let allViews = getViews() - var selectedBarButton: UIBarButtonItem? - var previousBarButton: [UIBarButtonItem] = [] + var selectedView: UIView? + var previousView: [UIView] = [] - selectedBarButton = allBarButtons[section] + selectedView = allViews[section] if section > 0 { - previousBarButton.append(allBarButtons[section - 1]) + previousView.append(allViews[section - 1]) + } + + if section + 1 < allViews.count { + previousView.append(allViews[section + 1]) } - if section + 1 < allBarButtons.count { - previousBarButton.append(allBarButtons[section + 1]) + return (previous: previousView, current: selectedView) + } + + func getViews() -> [UIView] { + + let navigationItem = parent.navigationItem + + var views: [UIView] = [] + + selectionSequence.forEach { + switch $0 { + case .leftBarButtons, .rightBarButtons: + + let leftBarButtons = navigationItem.leftBarButtonItems?.reversed() ?? [] + let rightBarButtons = navigationItem.rightBarButtonItems ?? [] + + let barButtons = $0 == .leftBarButtons ? leftBarButtons : rightBarButtons + + let convertedButtons = barButtons.map { + $0.customView as? UIButton + }.filter { + $0 != nil + } as! [UIButton] + + views += convertedButtons + + case .button(let button): + views.append(button) + + case .searchBar(let searchBar): + views.append(searchBar) + } } - return (previous: previousBarButton, current: selectedBarButton) + return views } /// Get CGPath for static circles (circles that do not expand) /// - Parameter button: Button to inherit path /// - Returns: CGPath that can be added into the layer - func getStaticCirclePath(button: UIButton) -> CGPath { + func getStaticCirclePath(button: UIView) -> CGPath { let maxWidth = button.frame.width + ScrollSelection.edgeOffset * 2 let maxHeight = button.frame.height + ScrollSelection.edgeOffset * 2 @@ -146,7 +180,7 @@ public class ScrollSelection { /// - multiplier: A value, from 0.0 to 1.0 to show how much to expand/shrink path /// - button: Button to inherit the path /// - Returns: CGPath that can be added into the layer - func getExpandingCirclePath(with multiplier: CGFloat, button: UIButton) -> CGPath { + func getExpandingCirclePath(with multiplier: CGFloat, button: UIView) -> CGPath { let maxWidth = button.frame.width + ScrollSelection.edgeOffset * 2 let maxHeight = button.frame.height + ScrollSelection.edgeOffset * 2 @@ -166,20 +200,6 @@ public class ScrollSelection { transform: nil) } - /// Deselect custom button, reset it to default - /// - Parameter button: Button to be deselected - func deselectCustomButton(_ button: UIButton) { - if let shapeLayer = button.layer.sublayers?.first as? CAShapeLayer { - - button.tintColor = .systemBlue - - shapeLayer.path = CGPath(roundedRect: .zero, - cornerWidth: .zero, - cornerHeight: .zero, - transform: nil) - } - } - /// Play haptic feedback based on current section /// /// This function also ensures that haptic feedback is not played multiple times in the same section @@ -231,7 +251,6 @@ public class ScrollSelection { // Create shape layer to get fill color let layer: CAShapeLayer = .init() - layer.fillColor = UIColor.systemGray4.cgColor layer.path = CGPath(roundedRect: .zero, cornerWidth: .zero, @@ -274,6 +293,50 @@ extension ScrollSelection { } } +// MARK: - Deselect +extension ScrollSelection { + func deselectAll(views: [UIView]) { + views.forEach { + deselectView($0) + } + } + + func deselectView(_ view: UIView) { + if let button = view as? UIButton { + deselectCustomButton(button) + + } else if let searchBar = view as? UISearchBar { + deselectSearchBar(searchBar) + + } + } + + /// Deselect custom button, reset it to default + /// - Parameter button: Button to be deselected + func deselectCustomButton(_ button: UIButton) { + if let shapeLayer = button.layer.sublayers?.first as? CAShapeLayer { + + button.tintColor = .systemBlue + + shapeLayer.path = CGPath(roundedRect: .zero, + cornerWidth: .zero, + cornerHeight: .zero, + transform: nil) + } + } + + func deselectSearchBar(_ searchBar: UISearchBar) { + + if let shapeLayer = searchBar.searchTextField.superview?.layer.sublayers?.first as? CAShapeLayer { + + shapeLayer.path = CGPath(roundedRect: .zero, + cornerWidth: .zero, + cornerHeight: .zero, + transform: nil) + } + } +} + // MARK: - ScrollView Delegate extension ScrollSelection { /// Update ScrollSelection when the scrollview scrolls @@ -301,10 +364,35 @@ extension ScrollSelection { if let section = getCurrentSection(offset) { // Get current and previous buttons - let buttons = getSelectedButtons(atSection: section) + let buttons = getSelectedViews(atSection: section) - if let button = buttons.current?.customView as? UIButton, - let shapeLayer = button.layer.sublayers?.first as? CAShapeLayer { + if let button = buttons.current { + let shapeLayer: CAShapeLayer = { + if let layer = button.layer.sublayers?.first as? CAShapeLayer { + return layer + } + + var layerView = button + + if let searchBar = button as? UISearchBar { + layerView = searchBar.searchTextField.superview! + + if let layer = layerView.layer.sublayers?.first as? CAShapeLayer { + return layer + } + } + + let layer: CAShapeLayer = .init() + + layer.path = CGPath(roundedRect: .zero, + cornerWidth: .zero, + cornerHeight: .zero, + transform: nil) + + layerView.layer.insertSublayer(layer, at: 0) + + return layer + }() var multiplier = (offset / -offsetMultiplier) - CGFloat(section + 1) @@ -343,20 +431,13 @@ extension ScrollSelection { playHaptics(withSection: section) - buttons.previous.forEach { - if let button = $0.customView as? UIButton { - deselectCustomButton(button) - } - } + deselectAll(views: buttons.previous) } else { currentSection = -1 - let leftBarButtons = parent.navigationItem.leftBarButtonItems?.first - let rightBarButtons = parent.navigationItem.rightBarButtonItems?.first - - if let lastButton = (leftBarButtons ?? rightBarButtons)?.customView as? UIButton { - deselectCustomButton(lastButton) + if let lastButton = getViews().first { + deselectView(lastButton) } } } else { @@ -384,14 +465,15 @@ extension ScrollSelection { // Don't launch buttons if it is inactive if !isActive { return } - let leftBarButtons = parent.navigationItem.leftBarButtonItems ?? [] - let rightBarButtons = parent.navigationItem.rightBarButtonItems ?? [] + let views = getViews() if let offset = scrollView?.contentOffset.y { if let section = getCurrentSection(offset) { - let buttons = rightBarButtons + leftBarButtons - if let button = buttons[section].customView as? UIButton { + + let view = views[section] + + if let button = view as? UIButton { if let actions = button.actions(forTarget: parent, forControlEvent: .touchUpInside) { actions.forEach { @@ -400,13 +482,11 @@ extension ScrollSelection { waitUntilDone: true) } } + } else if let searchBar = view as? UISearchBar { + searchBar.becomeFirstResponder() } - buttons.forEach { - if let button = $0.customView as? UIButton { - deselectCustomButton(button) - } - } + deselectAll(views: views) } } } @@ -431,16 +511,9 @@ extension ScrollSelection { // Don't reset to default if scrollSelection is inactive if !isActive { return } - let leftBarButtons = parent.navigationItem.leftBarButtonItems ?? [] - let rightBarButtons = parent.navigationItem.rightBarButtonItems ?? [] - - let buttons = rightBarButtons + leftBarButtons + let views = getViews() - buttons.forEach { - if let button = $0.customView as? UIButton { - deselectCustomButton(button) - } - } + deselectAll(views: views) } } @@ -503,7 +576,7 @@ extension ScrollSelection { /// - expands: If true, circular highlights will expand radially to show emphasis on the button as /// the user scrolls up. Otherwise, it will stay static and the highlight will not expand. /// - Returns: A scroll selection style - public static func circularHighlight(using color: UIColor = .systemGray4, + public static func circularHighlight(using color: UIColor = .systemGray5, expands: Bool = true) -> Style { var style = Style(rawValue: 1 << 1) style.color = color @@ -562,6 +635,14 @@ extension ScrollSelection { /// ``` case variableDecreasing } + + public enum SelectionView: Equatable { + + case leftBarButtons + case rightBarButtons + case button(_ button: UIButton) + case searchBar(_ searchBar: UISearchBar) + } } // MARK: UIViewController Implementation