From eb3d6b7a34d8696c35e4492f4fdc311b8e60b252 Mon Sep 17 00:00:00 2001 From: vasavk Date: Tue, 19 Mar 2024 17:49:01 +0530 Subject: [PATCH 01/13] Digital ACT-191 ONEAPP-6830 story: added new Carousel Scrollbar page and change log file --- VDS.xcodeproj/project.pbxproj | 16 + .../CarouselScrollbar/CarouselScrollbar.swift | 381 ++++++++++++++++++ .../CarouselScrollbarChangeLog.txt | 28 ++ 3 files changed, 425 insertions(+) create mode 100644 VDS/Components/CarouselScrollbar/CarouselScrollbar.swift create mode 100644 VDS/Components/CarouselScrollbar/CarouselScrollbarChangeLog.txt diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 63549b92..7c6ddcd1 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; }; + 1808BEC02BA456B700129230 /* CarouselScrollbarChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */; }; 186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */; }; 18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */; }; 18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */; }; @@ -178,6 +180,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = ""; }; + 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = ""; }; 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TextAreaChangeLog.txt; sourceTree = ""; }; 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconBadgeIndicatorModel.swift; sourceTree = ""; }; 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = ""; }; @@ -362,6 +366,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1808BEBA2BA41B1D00129230 /* CarouselScrollbar */ = { + isa = PBXGroup; + children = ( + 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */, + 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */, + ); + path = CarouselScrollbar; + sourceTree = ""; + }; 445BA07629C07ABA0036A7C5 /* Notification */ = { isa = PBXGroup; children = ( @@ -496,6 +509,7 @@ EA4DB2FE28DCBC1900103EE3 /* Badge */, EAD062AE2A3B87210015965D /* BadgeIndicator */, EA0FC2BE2912D18200DF80B4 /* Buttons */, + 1808BEBA2BA41B1D00129230 /* CarouselScrollbar */, EAF7F092289985E200B287F5 /* Checkbox */, EA985BF3296C609E00F2FF2E /* Icon */, EA3362412892EF700071C351 /* Label */, @@ -968,6 +982,7 @@ EAEEECA22B1F92AD00531FC2 /* LabelChangeLog.txt in Resources */, EA3362072891E14D0071C351 /* VerizonNHGeDS-Regular.otf in Resources */, EAEEEC9A2B1F8E4400531FC2 /* TextLinkChangeLog.txt in Resources */, + 1808BEC02BA456B700129230 /* CarouselScrollbarChangeLog.txt in Resources */, EAEEECAF2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt in Resources */, EAEEEC922B1F807300531FC2 /* BadgeChangeLog.txt in Resources */, EAEEEC9E2B1F8F7700531FC2 /* ButtonGroupChangeLog.txt in Resources */, @@ -1082,6 +1097,7 @@ EA3361AF288B26310071C351 /* FormFieldable.swift in Sources */, EA513A952A4E1F82002A4DFF /* TitleLockupStyleConfiguration.swift in Sources */, 44604AD729CE196600E62B51 /* Line.swift in Sources */, + 1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */, EAF978212A99035B00C2FEA9 /* Enabling.swift in Sources */, EA5E3058295105A40082B959 /* Tilelet.swift in Sources */, EA89201528B56CF4006B9984 /* RadioBoxGroup.swift in Sources */, diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift new file mode 100644 index 00000000..8e60f4f9 --- /dev/null +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -0,0 +1,381 @@ +// +// CarouselScrollbar.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 12/03/24. +// + +import Foundation +import UIKit +import VDSColorTokens +import VDSFormControlsTokens +import Combine + +/// A carousel scrollbar is a control that allows to navigate between items in a carousel. +/// It's also a status indicator that conveys the relative amount of content in a carousel and a location within it. +@objc(VDSCarouselScrollbar) +open class CarouselScrollbar: View { + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + required public init() { + super.init(frame: .zero) + } + + public override init(frame: CGRect) { + super.init(frame: .zero) + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + /// TO Do: take three view containers with fixed sizes - track view, active track view, thumb view. + /// add pan gesture to background container + /// set variable width to thumb container based on inputs. add pan gesture to track. + /// Not applicable to touch or keyboard devices - set width to 8 for three views if is in active state + /// scrollbar track only changes color on the side of the scrollbar that was interacted with. Track color changes will apply to whatever state the scrollbar is in before becoming active. if hovered via mouse, the scrollbar height will be increased when active. For touch or keyboard devices, the scrollbar will be the same size as the default state when the active track color change is applied. + /// + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + + // Sizes are from InVision design specs. + internal var containerSize: CGSize { CGSize(width: 45, height: 44) } + + // TO DO: to be remove + // open var numberOfSlides: Int? { didSet { setNeedsUpdate() } } + + /// Used to set total number of slides within carousel + open var numberOfSlides: Int? { + get { return _numberOfSlides } + set { + if let newValue { + _numberOfSlides = newValue + } else { + _numberOfSlides = 1 + } + setThumbWidth() + setNeedsUpdate() + } + } + + /// The amount of slides visible in the carousel container at one time. + open var selectedLayout: Layout? { + get { return _selectedLayout } + set { + if let newValue { + _selectedLayout = newValue + } else { + _selectedLayout = .oneUP + } + setThumbWidth() + setNeedsUpdate() + } + } + + /// Enum used to describe the amount of slides visible in the carousel container at one time. + public enum Layout: String, CaseIterable { + case oneUP = "1UP" + case twoUP = "2UP" + case threeUP = "3UP" + case fourUP = "4UP" + case fiveUP = "5UP" + case sixUP = "6UP" + case eightUP = "8UP" + + var value: Int { + switch self { + case .oneUP: + 1 + case .twoUP: + 2 + case .threeUP: + 3 + case .fourUP: + 4 + case .fiveUP: + 5 + case .sixUP: + 6 + case .eightUP: + 8 + } + } + } + + /// Used to set the position of the thumb(scrubber). This is used when the carousel container changes position, it will align the position of thumb(scrubber). + open var position: Int? { didSet { setNeedsUpdate() } } + + /// Allows a unique id to be passed into the thumb and track of the thumb(scrubber). + open var scrubberId: Int? { didSet { setNeedsUpdate() } } + + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + + private let trackViewWidth = 96 + private let trackViewHeight: CGFloat = 4 + private let minThumbWidth: Float = 16.0 + + private var thumbWidth: Float = 16.0 + private var actualThumbWidth: Float = 0.0 + + private var _selectedLayout: Layout = .oneUP + + private var _numberOfSlides: Int? = 1 + + internal var cornerRadius: CGFloat = 4.0 + internal var leftOverlayWidth: NSLayoutConstraint? + internal var rightOverlayXPos: NSLayoutConstraint? + internal var rightOverlayTrailingConstraint: NSLayoutConstraint? + + internal var heightConstraint: NSLayoutConstraint? + + + internal var containerView: UIView = { + return UIView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + }() + + /// Track view with fixed width + internal var trackView: UIView = { + return UIView().with { + $0.translatesAutoresizingMaskIntoConstraints = false +// $0.tag = 1 + } + }() + + /// Left Active Track overlay with variable width + internal var leftActiveOverlay: UIView = { + return UIView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.tag = 2 + } + }() + + /// Right Active Track overlay with variable width + internal var rightActiveOverlay: UIView = { + return UIView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.tag = 3 + } + }() + + /// Thumb view with variable width + internal var thumbView: UIView = { + return UIView().with { + $0.translatesAutoresizingMaskIntoConstraints = false +// $0.tag = 4 + } + }() + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func initialSetup() { + super.initialSetup() + } + + open override func setup() { + super.setup() + accessibilityLabel = "Carousel Scrollbar" + + //create the wrapping view + heightConstraint = self.heightAnchor.constraint(equalToConstant: containerSize.height) + heightConstraint?.priority = .defaultHigh + heightConstraint?.isActive = true + + //Trackview + trackView.frame = CGRectMake(20, 20, CGFloat(trackViewWidth), trackViewHeight) +// trackView.backgroundColor = .blue +// trackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))) +// trackView.isUserInteractionEnabled = true + trackView.layer.cornerRadius = cornerRadius + addSubview(trackView) + + ///Left active overlay width variable depends on thumb position + leftActiveOverlay.frame = CGRectMake(trackView.frame.origin.x, 20, CGFloat(trackViewWidth), trackViewHeight) +// leftActiveOverlay.backgroundColor = .yellow + leftActiveOverlay.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))) + leftActiveOverlay.isUserInteractionEnabled = true + leftActiveOverlay.layer.cornerRadius = cornerRadius + addSubview(leftActiveOverlay) + + ///Right active overlay width variable depends on thumbView position & trailing positon of trackView + rightActiveOverlay.frame = CGRectMake(thumbView.frame.origin.x + thumbView.frame.size.width, 20, CGFloat(trackViewWidth), trackViewHeight) +// rightActiveOverlay.backgroundColor = .orange + rightActiveOverlay.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))) + rightActiveOverlay.isUserInteractionEnabled = true + rightActiveOverlay.layer.cornerRadius = cornerRadius + addSubview(rightActiveOverlay) + + //Thumbview + thumbView.frame = CGRectMake(20, 20, CGFloat(thumbWidth), trackViewHeight) +// thumbView.backgroundColor = .green + thumbView.layer.cornerRadius = cornerRadius +// thumbView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))) +// thumbView.isUserInteractionEnabled = true + thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(self.handleGesture(_:))))) + addSubview(thumbView) + updateFrames() + } + + open override func updateView() { + super.updateView() +// updateFrames() + //left active overlay origin - x, width +// leftActiveOverlay.leadingAnchor.constraint(equalTo: trackView.leadingAnchor).activate() +// leftActiveOverlay.trailingAnchor.constraint(equalTo: thumbView.leadingAnchor).activate() + + //right active overlay origin - x, width, trailing position exact to trackView +// rightActiveOverlay.leadingAnchor.constraint(equalTo: thumbView.trailingAnchor).activate() +// rightActiveOverlay.trailingAnchor.constraint(equalTo: trackView.trailingAnchor).activate() + } + + open override func updateAccessibility() { + super.updateAccessibility() + } + + open override func reset() { + for subview in subviews { + for recognizer in subview.gestureRecognizers ?? [] { + subview.removeGestureRecognizer(recognizer) + } + } + super.reset() + } + +// open override func layoutSubviews() { +// super.layoutSubviews() +// setNeedsUpdate() +// } + + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + @objc func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { + // handling code + let tag = gestureRecognizer.view?.tag + switch tag! { +// case 1 : +// print("select first view") + case 2 : + thumbView.frame.origin.x = thumbView.frame.origin.x - CGFloat(actualThumbWidth) + updateFrames() +// print("select second view") + case 3 : + thumbView.frame.origin.x = thumbView.frame.origin.x + CGFloat(actualThumbWidth) + updateFrames() +// print("select third view") +// case 4 : +// print("select fourth view") + default: + print("default") + } + } + + @objc func handleGesture(_ sender: UIPanGestureRecognizer) { + + switch sender.state { + case .began: + print("began") + break + case .changed: + print("changed") + break + case .cancelled: + print("cancelled") + break + case .ended: + print("ended") + break + default: + break + } + } + + private func setThumbWidth() { + let width = Float (trackViewWidth / (numberOfSlides ?? 1) * _selectedLayout.value) + actualThumbWidth = width + thumbWidth = width < minThumbWidth ? minThumbWidth : width + thumbView.frame.size.width = CGFloat(thumbWidth) + thumbView.frame.origin.x = trackView.frame.origin.x + // print("trackViewWidth: \(trackViewWidth), numberOfSlides: \(String(describing: numberOfSlides)), tiles per set: \(_selectedLayout.value), actualThumbWidth: \(actualThumbWidth), thumbWidth: \(thumbWidth)") + //carousel position also to be update accordingly + updateFrames() + } + + //Known bug: when actual width is less than minimum width +// private func updateThumbFrame() { +// +// //right active overlay origin - x, width, trailing position exact to trackView +// // adjusting thumb position if it goes beyond trackView. +// let position1 = thumbView.frame.origin.x + thumbView.frame.size.width +// let position2 = trackView.frame.origin.x + trackView.frame.size.width +// if position1 > position2 { +// thumbView.frame.origin.x = position2 - thumbView.frame.size.width +// } else if thumbView.frame.origin.x < 0 { +// thumbView.frame.origin.x = trackView.frame.origin.x +// } else { +// rightActiveOverlay.frame.origin.x = position1 +// rightActiveOverlay.frame.size.width = position2 - position1 +// } +// DispatchQueue.main.asyncAfter(deadline: .now() + 2 , execute: { +// //left active overlay origin - x, width +// self.updateOverlay() +// }) +// } +// +// private func updateOverlay() { +// //left active overlay origin - x, width +// leftActiveOverlay.frame.size.width = thumbView.frame.origin.x - trackView.frame.origin.x +// } +// + private func updateFrames() { + //left active overlay origin - x, width + leftActiveOverlay.frame.size.width = thumbView.frame.origin.x - trackView.frame.origin.x + + //right active overlay origin - x, width, trailing position exact to trackView + let position1 = thumbView.frame.origin.x + thumbView.frame.size.width + let position2 = trackView.frame.origin.x + trackView.frame.size.width + rightActiveOverlay.frame.origin.x = position1 + rightActiveOverlay.frame.size.width = position2 - position1 + } +} + + + + + + + +//private func updateFrames() { +// //right active overlay origin - x, width, trailing position exact to trackView +// let position1 = thumbView.frame.origin.x + thumbView.frame.size.width +// let position2 = trackView.frame.origin.x + trackView.frame.size.width +// rightActiveOverlay.frame.origin.x = position1 +// rightActiveOverlay.frame.size.width = position2 - position1 + + +////right active overlay origin - x, width, trailing position exact to trackView +//// adjusting thumb position if it goes beyond trackView. +//let position1 = thumbView.frame.origin.x + thumbView.frame.size.width +//let position2 = trackView.frame.origin.x + trackView.frame.size.width +//if position1 > position2 { +// thumbView.frame.origin.x = position2 - thumbView.frame.size.width +//} else if thumbView.frame.origin.x < 0 { +// thumbView.frame.origin.x = trackView.frame.origin.x +//} else { +// rightActiveOverlay.frame.origin.x = position1 +// rightActiveOverlay.frame.size.width = position2 - position1 +//} diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbarChangeLog.txt b/VDS/Components/CarouselScrollbar/CarouselScrollbarChangeLog.txt new file mode 100644 index 00000000..68179e36 --- /dev/null +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbarChangeLog.txt @@ -0,0 +1,28 @@ +MM/DD/YYYY +---------------- + +07/29/22 +---------------- +- Initial Brand 3.0 handoff + +08/10/2022 +---------------- +- Updated default and inverted prop to light and dark surface. + +11/30/2022 +---------------- +- Added "(web only)" to any instance of "keyboard focus" + +12/13/2022 +---------------- +- Replaced focus border pixel and style & spacing values with tokens. + +01/09/2023 +---------------- +- Updated Specs to use new SPEC Templates and SPEC DOC Components. + +05/19/2023 +---------------- +- Changed Carousel Scrubber to Carousel Scrollbar and replaced all instances of Scrubber to Scrollbar. +- Removed KF states and behaviors from States and Behaviors > Interaction Types. + From 11618e88a0f2a511422c179ac816b29915e5d71e Mon Sep 17 00:00:00 2001 From: vasavk Date: Wed, 20 Mar 2024 23:14:04 +0530 Subject: [PATCH 02/13] Digital ACT-191 ONEAPP-6830 story: did set color configurations and removed unused code --- .../CarouselScrollbar/CarouselScrollbar.swift | 174 ++++-------------- 1 file changed, 34 insertions(+), 140 deletions(-) diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index 8e60f4f9..a0523342 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -30,29 +30,10 @@ open class CarouselScrollbar: View { public required init?(coder: NSCoder) { super.init(coder: coder) } - - /// TO Do: take three view containers with fixed sizes - track view, active track view, thumb view. - /// add pan gesture to background container - /// set variable width to thumb container based on inputs. add pan gesture to track. - /// Not applicable to touch or keyboard devices - set width to 8 for three views if is in active state - /// scrollbar track only changes color on the side of the scrollbar that was interacted with. Track color changes will apply to whatever state the scrollbar is in before becoming active. if hovered via mouse, the scrollbar height will be increased when active. For touch or keyboard devices, the scrollbar will be the same size as the default state when the active track color change is applied. - /// - + //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- - - // Sizes are from InVision design specs. - internal var containerSize: CGSize { CGSize(width: 45, height: 44) } - - // TO DO: to be remove - // open var numberOfSlides: Int? { didSet { setNeedsUpdate() } } - /// Used to set total number of slides within carousel open var numberOfSlides: Int? { get { return _numberOfSlides } @@ -113,7 +94,7 @@ open class CarouselScrollbar: View { /// Used to set the position of the thumb(scrubber). This is used when the carousel container changes position, it will align the position of thumb(scrubber). open var position: Int? { didSet { setNeedsUpdate() } } - + /// Allows a unique id to be passed into the thumb and track of the thumb(scrubber). open var scrubberId: Int? { didSet { setNeedsUpdate() } } @@ -121,37 +102,23 @@ open class CarouselScrollbar: View { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- + // Sizes are from InVision design specs. + internal var containerSize: CGSize { CGSize(width: 45, height: 44) } private let trackViewWidth = 96 private let trackViewHeight: CGFloat = 4 private let minThumbWidth: Float = 16.0 - private var thumbWidth: Float = 16.0 private var actualThumbWidth: Float = 0.0 - private var _selectedLayout: Layout = .oneUP - private var _numberOfSlides: Int? = 1 - internal var cornerRadius: CGFloat = 4.0 - internal var leftOverlayWidth: NSLayoutConstraint? - internal var rightOverlayXPos: NSLayoutConstraint? - internal var rightOverlayTrailingConstraint: NSLayoutConstraint? - internal var heightConstraint: NSLayoutConstraint? - - - internal var containerView: UIView = { - return UIView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - } - }() /// Track view with fixed width internal var trackView: UIView = { return UIView().with { $0.translatesAutoresizingMaskIntoConstraints = false -// $0.tag = 1 } }() @@ -175,10 +142,16 @@ open class CarouselScrollbar: View { internal var thumbView: UIView = { return UIView().with { $0.translatesAutoresizingMaskIntoConstraints = false -// $0.tag = 4 } }() + //-------------------------------------------------- + // MARK: - Configuration + //-------------------------------------------------- + private var thumbColorConfiguration = SurfaceColorConfiguration(VDSColor.interactiveScrollthumbOnlight , VDSColor.interactiveScrollthumbOndark) + private var trackColorConfiguration = SurfaceColorConfiguration(VDSColor.interactiveScrolltrackOnlight , VDSColor.interactiveScrolltrackOndark) + private var activeOverlayColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteWhite) + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -194,26 +167,21 @@ open class CarouselScrollbar: View { heightConstraint = self.heightAnchor.constraint(equalToConstant: containerSize.height) heightConstraint?.priority = .defaultHigh heightConstraint?.isActive = true - + //Trackview trackView.frame = CGRectMake(20, 20, CGFloat(trackViewWidth), trackViewHeight) -// trackView.backgroundColor = .blue -// trackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))) -// trackView.isUserInteractionEnabled = true trackView.layer.cornerRadius = cornerRadius addSubview(trackView) - - ///Left active overlay width variable depends on thumb position + + ///Left active overlay leftActiveOverlay.frame = CGRectMake(trackView.frame.origin.x, 20, CGFloat(trackViewWidth), trackViewHeight) -// leftActiveOverlay.backgroundColor = .yellow leftActiveOverlay.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))) leftActiveOverlay.isUserInteractionEnabled = true leftActiveOverlay.layer.cornerRadius = cornerRadius addSubview(leftActiveOverlay) - ///Right active overlay width variable depends on thumbView position & trailing positon of trackView + ///Right active overlay rightActiveOverlay.frame = CGRectMake(thumbView.frame.origin.x + thumbView.frame.size.width, 20, CGFloat(trackViewWidth), trackViewHeight) -// rightActiveOverlay.backgroundColor = .orange rightActiveOverlay.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))) rightActiveOverlay.isUserInteractionEnabled = true rightActiveOverlay.layer.cornerRadius = cornerRadius @@ -221,31 +189,22 @@ open class CarouselScrollbar: View { //Thumbview thumbView.frame = CGRectMake(20, 20, CGFloat(thumbWidth), trackViewHeight) -// thumbView.backgroundColor = .green thumbView.layer.cornerRadius = cornerRadius -// thumbView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))) -// thumbView.isUserInteractionEnabled = true thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(self.handleGesture(_:))))) addSubview(thumbView) updateFrames() - } + } - open override func updateView() { + open override func updateView() { super.updateView() -// updateFrames() - //left active overlay origin - x, width -// leftActiveOverlay.leadingAnchor.constraint(equalTo: trackView.leadingAnchor).activate() -// leftActiveOverlay.trailingAnchor.constraint(equalTo: thumbView.leadingAnchor).activate() - - //right active overlay origin - x, width, trailing position exact to trackView -// rightActiveOverlay.leadingAnchor.constraint(equalTo: thumbView.trailingAnchor).activate() -// rightActiveOverlay.trailingAnchor.constraint(equalTo: trackView.trailingAnchor).activate() + trackView.backgroundColor = trackColorConfiguration.getColor(surface) + thumbView.backgroundColor = thumbColorConfiguration.getColor(surface) } open override func updateAccessibility() { super.updateAccessibility() } - + open override func reset() { for subview in subviews { for recognizer in subview.gestureRecognizers ?? [] { @@ -255,37 +214,26 @@ open class CarouselScrollbar: View { super.reset() } -// open override func layoutSubviews() { -// super.layoutSubviews() -// setNeedsUpdate() -// } - //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- @objc func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { // handling code let tag = gestureRecognizer.view?.tag - switch tag! { -// case 1 : -// print("select first view") - case 2 : - thumbView.frame.origin.x = thumbView.frame.origin.x - CGFloat(actualThumbWidth) - updateFrames() -// print("select second view") - case 3 : - thumbView.frame.origin.x = thumbView.frame.origin.x + CGFloat(actualThumbWidth) - updateFrames() -// print("select third view") -// case 4 : -// print("select fourth view") - default: - print("default") - } + switch tag! { + case 2 : + thumbView.frame.origin.x = thumbView.frame.origin.x - CGFloat(actualThumbWidth) + updateFrames() + case 3 : + thumbView.frame.origin.x = thumbView.frame.origin.x + CGFloat(actualThumbWidth) + updateFrames() + default: + print("default") + } } @objc func handleGesture(_ sender: UIPanGestureRecognizer) { - + switch sender.state { case .began: print("began") @@ -306,46 +254,18 @@ open class CarouselScrollbar: View { private func setThumbWidth() { let width = Float (trackViewWidth / (numberOfSlides ?? 1) * _selectedLayout.value) - actualThumbWidth = width - thumbWidth = width < minThumbWidth ? minThumbWidth : width + actualThumbWidth = (width > Float(trackViewWidth)) ? Float(trackViewWidth) : width + thumbWidth = (width < Float(trackViewWidth) && width > minThumbWidth) ? width : ((width > Float(trackViewWidth)) ? Float(trackViewWidth) : minThumbWidth) thumbView.frame.size.width = CGFloat(thumbWidth) thumbView.frame.origin.x = trackView.frame.origin.x - // print("trackViewWidth: \(trackViewWidth), numberOfSlides: \(String(describing: numberOfSlides)), tiles per set: \(_selectedLayout.value), actualThumbWidth: \(actualThumbWidth), thumbWidth: \(thumbWidth)") - //carousel position also to be update accordingly updateFrames() } - //Known bug: when actual width is less than minimum width -// private func updateThumbFrame() { -// -// //right active overlay origin - x, width, trailing position exact to trackView -// // adjusting thumb position if it goes beyond trackView. -// let position1 = thumbView.frame.origin.x + thumbView.frame.size.width -// let position2 = trackView.frame.origin.x + trackView.frame.size.width -// if position1 > position2 { -// thumbView.frame.origin.x = position2 - thumbView.frame.size.width -// } else if thumbView.frame.origin.x < 0 { -// thumbView.frame.origin.x = trackView.frame.origin.x -// } else { -// rightActiveOverlay.frame.origin.x = position1 -// rightActiveOverlay.frame.size.width = position2 - position1 -// } -// DispatchQueue.main.asyncAfter(deadline: .now() + 2 , execute: { -// //left active overlay origin - x, width -// self.updateOverlay() -// }) -// } -// -// private func updateOverlay() { -// //left active overlay origin - x, width -// leftActiveOverlay.frame.size.width = thumbView.frame.origin.x - trackView.frame.origin.x -// } -// private func updateFrames() { //left active overlay origin - x, width leftActiveOverlay.frame.size.width = thumbView.frame.origin.x - trackView.frame.origin.x - //right active overlay origin - x, width, trailing position exact to trackView + //right active overlay origin - x, width let position1 = thumbView.frame.origin.x + thumbView.frame.size.width let position2 = trackView.frame.origin.x + trackView.frame.size.width rightActiveOverlay.frame.origin.x = position1 @@ -353,29 +273,3 @@ open class CarouselScrollbar: View { } } - - - - - - -//private func updateFrames() { -// //right active overlay origin - x, width, trailing position exact to trackView -// let position1 = thumbView.frame.origin.x + thumbView.frame.size.width -// let position2 = trackView.frame.origin.x + trackView.frame.size.width -// rightActiveOverlay.frame.origin.x = position1 -// rightActiveOverlay.frame.size.width = position2 - position1 - - -////right active overlay origin - x, width, trailing position exact to trackView -//// adjusting thumb position if it goes beyond trackView. -//let position1 = thumbView.frame.origin.x + thumbView.frame.size.width -//let position2 = trackView.frame.origin.x + trackView.frame.size.width -//if position1 > position2 { -// thumbView.frame.origin.x = position2 - thumbView.frame.size.width -//} else if thumbView.frame.origin.x < 0 { -// thumbView.frame.origin.x = trackView.frame.origin.x -//} else { -// rightActiveOverlay.frame.origin.x = position1 -// rightActiveOverlay.frame.size.width = position2 - position1 -//} From fadb996c6e24b581f00c4d8c6dd19bc27a137242 Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 21 Mar 2024 01:49:10 +0530 Subject: [PATCH 03/13] Digital ACT-191 ONEAPP-6830 story: scrollbar thumb advancement logic, changes to active track color and opacity. --- .../CarouselScrollbar/CarouselScrollbar.swift | 106 ++++++++++++------ 1 file changed, 74 insertions(+), 32 deletions(-) diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index a0523342..918c4864 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -104,16 +104,18 @@ open class CarouselScrollbar: View { //-------------------------------------------------- // Sizes are from InVision design specs. internal var containerSize: CGSize { CGSize(width: 45, height: 44) } - + internal var _selectedLayout: Layout = .oneUP + internal var _numberOfSlides: Int? = 1 + internal var heightConstraint: NSLayoutConstraint? + private let trackViewWidth = 96 private let trackViewHeight: CGFloat = 4 private let minThumbWidth: Float = 16.0 private var thumbWidth: Float = 16.0 private var actualThumbWidth: Float = 0.0 - private var _selectedLayout: Layout = .oneUP - private var _numberOfSlides: Int? = 1 - internal var cornerRadius: CGFloat = 4.0 - internal var heightConstraint: NSLayoutConstraint? + private let cornerRadius: CGFloat = 4.0 + private let activeOpacity: Float = 0.15 + private let defaultOpacity: Float = 1 /// Track view with fixed width internal var trackView: UIView = { @@ -172,19 +174,23 @@ open class CarouselScrollbar: View { trackView.frame = CGRectMake(20, 20, CGFloat(trackViewWidth), trackViewHeight) trackView.layer.cornerRadius = cornerRadius addSubview(trackView) - + ///Left active overlay leftActiveOverlay.frame = CGRectMake(trackView.frame.origin.x, 20, CGFloat(trackViewWidth), trackViewHeight) - leftActiveOverlay.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))) leftActiveOverlay.isUserInteractionEnabled = true leftActiveOverlay.layer.cornerRadius = cornerRadius + let leftPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.leftLongPressed(_:))) + leftPressRecognizer.minimumPressDuration = 0 + leftActiveOverlay.addGestureRecognizer(leftPressRecognizer) addSubview(leftActiveOverlay) ///Right active overlay rightActiveOverlay.frame = CGRectMake(thumbView.frame.origin.x + thumbView.frame.size.width, 20, CGFloat(trackViewWidth), trackViewHeight) - rightActiveOverlay.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))) rightActiveOverlay.isUserInteractionEnabled = true rightActiveOverlay.layer.cornerRadius = cornerRadius + let rightPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.rightLongPressed(_:))) + rightPressRecognizer.minimumPressDuration = 0 + rightActiveOverlay.addGestureRecognizer(rightPressRecognizer) addSubview(rightActiveOverlay) //Thumbview @@ -192,7 +198,7 @@ open class CarouselScrollbar: View { thumbView.layer.cornerRadius = cornerRadius thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(self.handleGesture(_:))))) addSubview(thumbView) - updateFrames() + updateActiveOverlayFrames() } open override func updateView() { @@ -217,21 +223,17 @@ open class CarouselScrollbar: View { //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- - @objc func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { - // handling code - let tag = gestureRecognizer.view?.tag - switch tag! { - case 2 : - thumbView.frame.origin.x = thumbView.frame.origin.x - CGFloat(actualThumbWidth) - updateFrames() - case 3 : - thumbView.frame.origin.x = thumbView.frame.origin.x + CGFloat(actualThumbWidth) - updateFrames() - default: - print("default") - } + func updateThumbViewToLeft() { + thumbView.frame.origin.x = thumbView.frame.origin.x - CGFloat(actualThumbWidth) + updateActiveOverlayFrames() } + func updateThumbViewToRight() { + thumbView.frame.origin.x = thumbView.frame.origin.x + CGFloat(actualThumbWidth) + updateActiveOverlayFrames() + } + + //TO DO: Drag functionality pending @objc func handleGesture(_ sender: UIPanGestureRecognizer) { switch sender.state { @@ -252,24 +254,64 @@ open class CarouselScrollbar: View { } } + @objc func leftLongPressed(_ gesture: UILongPressGestureRecognizer) { + if gesture.state == .began { + leftActiveOverlay.backgroundColor = activeOverlayColorConfiguration.getColor(self) + leftActiveOverlay.layer.opacity = activeOpacity + } else if gesture.state == .cancelled { + leftActiveOverlay.backgroundColor = .clear + leftActiveOverlay.layer.opacity = defaultOpacity + } else if gesture.state == .ended { + leftActiveOverlay.backgroundColor = .clear + leftActiveOverlay.layer.opacity = defaultOpacity + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in + self?.updateThumbViewToLeft() + } + } + } + + @objc func rightLongPressed(_ gesture: UILongPressGestureRecognizer) { + if gesture.state == .began { + rightActiveOverlay.backgroundColor = activeOverlayColorConfiguration.getColor(self) + rightActiveOverlay.layer.opacity = activeOpacity + } else if gesture.state == .cancelled { + rightActiveOverlay.backgroundColor = .clear + rightActiveOverlay.layer.opacity = defaultOpacity + } else if gesture.state == .ended { + rightActiveOverlay.backgroundColor = .clear + rightActiveOverlay.layer.opacity = defaultOpacity + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in + self?.updateThumbViewToRight() + } + } + } + private func setThumbWidth() { let width = Float (trackViewWidth / (numberOfSlides ?? 1) * _selectedLayout.value) actualThumbWidth = (width > Float(trackViewWidth)) ? Float(trackViewWidth) : width - thumbWidth = (width < Float(trackViewWidth) && width > minThumbWidth) ? width : ((width > Float(trackViewWidth)) ? Float(trackViewWidth) : minThumbWidth) + thumbWidth = (width <= Float(trackViewWidth) && width > minThumbWidth) ? width : ((width > Float(trackViewWidth)) ? Float(trackViewWidth) : minThumbWidth) thumbView.frame.size.width = CGFloat(thumbWidth) thumbView.frame.origin.x = trackView.frame.origin.x - updateFrames() + updateActiveOverlayFrames() } - private func updateFrames() { - //left active overlay origin - x, width - leftActiveOverlay.frame.size.width = thumbView.frame.origin.x - trackView.frame.origin.x + private func updateActiveOverlayFrames() { + // adjusting thumb position if it goes beyond trackView. + let thumbPosition = thumbView.frame.origin.x + thumbView.frame.size.width + let trackPosition = trackView.frame.origin.x + trackView.frame.size.width + if thumbPosition > trackPosition { + thumbView.frame.origin.x = trackPosition - thumbView.frame.size.width + } else if thumbView.frame.origin.x < trackView.frame.origin.x { + thumbView.frame.origin.x = trackView.frame.origin.x + } - //right active overlay origin - x, width - let position1 = thumbView.frame.origin.x + thumbView.frame.size.width - let position2 = trackView.frame.origin.x + trackView.frame.size.width - rightActiveOverlay.frame.origin.x = position1 - rightActiveOverlay.frame.size.width = position2 - position1 + //left active overlay position update + leftActiveOverlay.frame.size.width = thumbView.frame.origin.x - trackView.frame.origin.x + cornerRadius + + //left active overlay position update + rightActiveOverlay.frame.origin.x = thumbView.frame.origin.x + thumbView.frame.size.width - cornerRadius + rightActiveOverlay.frame.size.width = (trackView.frame.origin.x + trackView.frame.size.width) - (thumbView.frame.origin.x + thumbView.frame.size.width) + cornerRadius } } + From f7ef52251b805c95c2da477af0c25f0321b8a777 Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 21 Mar 2024 09:32:00 +0530 Subject: [PATCH 04/13] Digital ACT-191 ONEAPP-6830 story: Implementation for scroll thumb on given position value --- .../CarouselScrollbar/CarouselScrollbar.swift | 67 +++++++++++++------ 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index 918c4864..8ed8cfde 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -44,11 +44,12 @@ open class CarouselScrollbar: View { _numberOfSlides = 1 } setThumbWidth() + setThumb(position) setNeedsUpdate() } } - /// The amount of slides visible in the carousel container at one time. + /// The number of slides that can appear at once in a set in a carousel container. open var selectedLayout: Layout? { get { return _selectedLayout } set { @@ -58,11 +59,12 @@ open class CarouselScrollbar: View { _selectedLayout = .oneUP } setThumbWidth() + setThumb(position) setNeedsUpdate() } } - /// Enum used to describe the amount of slides visible in the carousel container at one time. + /// Enum used to describe the number of slides that can appear at once in a set in a carousel container. public enum Layout: String, CaseIterable { case oneUP = "1UP" case twoUP = "2UP" @@ -93,7 +95,19 @@ open class CarouselScrollbar: View { } /// Used to set the position of the thumb(scrubber). This is used when the carousel container changes position, it will align the position of thumb(scrubber). - open var position: Int? { didSet { setNeedsUpdate() } } + open var position: Int? { + get { return _position } + set { + if let newValue { + checkPositions() + _position = (newValue > totalPositions ?? 1) ? totalPositions : newValue + } else { + _position = 1 + } + setThumb(position) + setNeedsUpdate() + } + } /// Allows a unique id to be passed into the thumb and track of the thumb(scrubber). open var scrubberId: Int? { didSet { setNeedsUpdate() } } @@ -107,7 +121,9 @@ open class CarouselScrollbar: View { internal var _selectedLayout: Layout = .oneUP internal var _numberOfSlides: Int? = 1 internal var heightConstraint: NSLayoutConstraint? - + internal var totalPositions: Int? = 1 + internal var _position: Int? = 1 + private let trackViewWidth = 96 private let trackViewHeight: CGFloat = 4 private let minThumbWidth: Float = 16.0 @@ -179,7 +195,7 @@ open class CarouselScrollbar: View { leftActiveOverlay.frame = CGRectMake(trackView.frame.origin.x, 20, CGFloat(trackViewWidth), trackViewHeight) leftActiveOverlay.isUserInteractionEnabled = true leftActiveOverlay.layer.cornerRadius = cornerRadius - let leftPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.leftLongPressed(_:))) + let leftPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.onThumbTouchStart(_:))) leftPressRecognizer.minimumPressDuration = 0 leftActiveOverlay.addGestureRecognizer(leftPressRecognizer) addSubview(leftActiveOverlay) @@ -188,7 +204,7 @@ open class CarouselScrollbar: View { rightActiveOverlay.frame = CGRectMake(thumbView.frame.origin.x + thumbView.frame.size.width, 20, CGFloat(trackViewWidth), trackViewHeight) rightActiveOverlay.isUserInteractionEnabled = true rightActiveOverlay.layer.cornerRadius = cornerRadius - let rightPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.rightLongPressed(_:))) + let rightPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.onThumbTouchEnd(_:))) rightPressRecognizer.minimumPressDuration = 0 rightActiveOverlay.addGestureRecognizer(rightPressRecognizer) addSubview(rightActiveOverlay) @@ -198,7 +214,7 @@ open class CarouselScrollbar: View { thumbView.layer.cornerRadius = cornerRadius thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(self.handleGesture(_:))))) addSubview(thumbView) - updateActiveOverlayFrames() + updateActiveOverlays() } open override func updateView() { @@ -223,17 +239,16 @@ open class CarouselScrollbar: View { //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- - func updateThumbViewToLeft() { + func onMoveBackward() { thumbView.frame.origin.x = thumbView.frame.origin.x - CGFloat(actualThumbWidth) - updateActiveOverlayFrames() + updateActiveOverlays() } - func updateThumbViewToRight() { + func onMoveForward() { thumbView.frame.origin.x = thumbView.frame.origin.x + CGFloat(actualThumbWidth) - updateActiveOverlayFrames() + updateActiveOverlays() } - - //TO DO: Drag functionality pending + @objc func handleGesture(_ sender: UIPanGestureRecognizer) { switch sender.state { @@ -254,7 +269,7 @@ open class CarouselScrollbar: View { } } - @objc func leftLongPressed(_ gesture: UILongPressGestureRecognizer) { + @objc func onThumbTouchStart(_ gesture: UILongPressGestureRecognizer) { if gesture.state == .began { leftActiveOverlay.backgroundColor = activeOverlayColorConfiguration.getColor(self) leftActiveOverlay.layer.opacity = activeOpacity @@ -265,12 +280,12 @@ open class CarouselScrollbar: View { leftActiveOverlay.backgroundColor = .clear leftActiveOverlay.layer.opacity = defaultOpacity DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in - self?.updateThumbViewToLeft() + self?.onMoveBackward() } } } - @objc func rightLongPressed(_ gesture: UILongPressGestureRecognizer) { + @objc func onThumbTouchEnd(_ gesture: UILongPressGestureRecognizer) { if gesture.state == .began { rightActiveOverlay.backgroundColor = activeOverlayColorConfiguration.getColor(self) rightActiveOverlay.layer.opacity = activeOpacity @@ -281,21 +296,22 @@ open class CarouselScrollbar: View { rightActiveOverlay.backgroundColor = .clear rightActiveOverlay.layer.opacity = defaultOpacity DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in - self?.updateThumbViewToRight() + self?.onMoveForward() } } } private func setThumbWidth() { - let width = Float (trackViewWidth / (numberOfSlides ?? 1) * _selectedLayout.value) + let width = (Float(trackViewWidth) / Float(numberOfSlides ?? 1)) * Float(_selectedLayout.value) actualThumbWidth = (width > Float(trackViewWidth)) ? Float(trackViewWidth) : width thumbWidth = (width <= Float(trackViewWidth) && width > minThumbWidth) ? width : ((width > Float(trackViewWidth)) ? Float(trackViewWidth) : minThumbWidth) thumbView.frame.size.width = CGFloat(thumbWidth) thumbView.frame.origin.x = trackView.frame.origin.x - updateActiveOverlayFrames() + checkPositions() + updateActiveOverlays() } - private func updateActiveOverlayFrames() { + private func updateActiveOverlays() { // adjusting thumb position if it goes beyond trackView. let thumbPosition = thumbView.frame.origin.x + thumbView.frame.size.width let trackPosition = trackView.frame.origin.x + trackView.frame.size.width @@ -303,7 +319,7 @@ open class CarouselScrollbar: View { thumbView.frame.origin.x = trackPosition - thumbView.frame.size.width } else if thumbView.frame.origin.x < trackView.frame.origin.x { thumbView.frame.origin.x = trackView.frame.origin.x - } + } //left active overlay position update leftActiveOverlay.frame.size.width = thumbView.frame.origin.x - trackView.frame.origin.x + cornerRadius @@ -312,6 +328,15 @@ open class CarouselScrollbar: View { rightActiveOverlay.frame.origin.x = thumbView.frame.origin.x + thumbView.frame.size.width - cornerRadius rightActiveOverlay.frame.size.width = (trackView.frame.origin.x + trackView.frame.size.width) - (thumbView.frame.origin.x + thumbView.frame.size.width) + cornerRadius } + + private func checkPositions() { + totalPositions = Int (ceil (Double(numberOfSlides ?? 1) / Double(_selectedLayout.value))) + } + + private func setThumb(_ position: Int?) { + thumbView.frame.origin.x = CGFloat(Float((position ?? 1) - 1) * actualThumbWidth) + trackView.frame.origin.x + updateActiveOverlays() + } } From 51a00f9b68bc62307bb6332770557bb383dbf852 Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 21 Mar 2024 10:58:53 +0530 Subject: [PATCH 05/13] Digital ACT-191 ONEAPP-6830 story: Interaction type: Drag scrollbar thumb --- .../CarouselScrollbar/CarouselScrollbar.swift | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index 8ed8cfde..ca38bf03 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -44,7 +44,7 @@ open class CarouselScrollbar: View { _numberOfSlides = 1 } setThumbWidth() - setThumb(position) + scrollThumbToPosition(position) setNeedsUpdate() } } @@ -59,7 +59,7 @@ open class CarouselScrollbar: View { _selectedLayout = .oneUP } setThumbWidth() - setThumb(position) + scrollThumbToPosition(position) setNeedsUpdate() } } @@ -104,7 +104,7 @@ open class CarouselScrollbar: View { } else { _position = 1 } - setThumb(position) + scrollThumbToPosition(position) setNeedsUpdate() } } @@ -123,6 +123,7 @@ open class CarouselScrollbar: View { internal var heightConstraint: NSLayoutConstraint? internal var totalPositions: Int? = 1 internal var _position: Int? = 1 + internal var trayOriginalCenter: CGPoint! private let trackViewWidth = 96 private let trackViewHeight: CGFloat = 4 @@ -212,7 +213,7 @@ open class CarouselScrollbar: View { //Thumbview thumbView.frame = CGRectMake(20, 20, CGFloat(thumbWidth), trackViewHeight) thumbView.layer.cornerRadius = cornerRadius - thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(self.handleGesture(_:))))) + thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(self.onScrubberDrag(_:))))) addSubview(thumbView) updateActiveOverlays() } @@ -249,23 +250,17 @@ open class CarouselScrollbar: View { updateActiveOverlays() } - @objc func handleGesture(_ sender: UIPanGestureRecognizer) { - - switch sender.state { - case .began: - print("began") - break - case .changed: - print("changed") - break - case .cancelled: - print("cancelled") - break - case .ended: - print("ended") - break - default: - break + @objc func onScrubberDrag(_ sender: UIPanGestureRecognizer) { + let translation = sender.translation(in: thumbView) + if sender.state == UIGestureRecognizer.State.began { + trayOriginalCenter = thumbView.center + } else if sender.state == UIGestureRecognizer.State.changed { + let movePositions = Int (ceil (Double(translation.x) / Double(actualThumbWidth))) + setThumb(at: (position ?? 1) + movePositions) + } + else if sender.state == UIGestureRecognizer.State.cancelled || sender.state == UIGestureRecognizer.State.ended { + let movePositions = Int (ceil (Double(translation.x) / Double(actualThumbWidth))) + position = (position ?? 1) + movePositions } } @@ -332,8 +327,14 @@ open class CarouselScrollbar: View { private func checkPositions() { totalPositions = Int (ceil (Double(numberOfSlides ?? 1) / Double(_selectedLayout.value))) } + + private func scrollThumbToPosition(_ position: Int?) { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in + self?.setThumb(at: position) + } + } - private func setThumb(_ position: Int?) { + private func setThumb(at position: Int?) { thumbView.frame.origin.x = CGFloat(Float((position ?? 1) - 1) * actualThumbWidth) + trackView.frame.origin.x updateActiveOverlays() } From 1387cb6bf4e734ba93ffd197c194015a7893dff7 Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 21 Mar 2024 12:00:03 +0530 Subject: [PATCH 06/13] Digital ACT-191 ONEAPP-6830 story: update position when scroll thumb drag / on move forward / backward --- .../CarouselScrollbar/CarouselScrollbar.swift | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index ca38bf03..3671c27d 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -112,6 +112,8 @@ open class CarouselScrollbar: View { /// Allows a unique id to be passed into the thumb and track of the thumb(scrubber). open var scrubberId: Int? { didSet { setNeedsUpdate() } } + /// A callback when the scrubber position changes. Passes parameters (position). + open var onScrubberDidChange: ((Int) -> Void)? //-------------------------------------------------- // MARK: - Private Properties @@ -241,29 +243,32 @@ open class CarouselScrollbar: View { // MARK: - Private Methods //-------------------------------------------------- func onMoveBackward() { - thumbView.frame.origin.x = thumbView.frame.origin.x - CGFloat(actualThumbWidth) - updateActiveOverlays() + position = (position ?? 1) - 1 + scrollThumbToPosition(position) } func onMoveForward() { - thumbView.frame.origin.x = thumbView.frame.origin.x + CGFloat(actualThumbWidth) - updateActiveOverlays() + position = (position ?? 1) + 1 + scrollThumbToPosition(position) } - + + // Drag scrollbar thumb to move it to the left or right. + // Upon releases of drag, the scrollbar thumb snaps to the closest full position and the scrollbar returns to original size without delay. @objc func onScrubberDrag(_ sender: UIPanGestureRecognizer) { let translation = sender.translation(in: thumbView) if sender.state == UIGestureRecognizer.State.began { trayOriginalCenter = thumbView.center } else if sender.state == UIGestureRecognizer.State.changed { - let movePositions = Int (ceil (Double(translation.x) / Double(actualThumbWidth))) - setThumb(at: (position ?? 1) + movePositions) + let draggedPositions = Int (ceil (Double(translation.x) / Double(actualThumbWidth))) + setThumb(at: (position ?? 1) + draggedPositions) } else if sender.state == UIGestureRecognizer.State.cancelled || sender.state == UIGestureRecognizer.State.ended { - let movePositions = Int (ceil (Double(translation.x) / Double(actualThumbWidth))) - position = (position ?? 1) + movePositions + let draggedPositions = Int (ceil (Double(translation.x) / Double(actualThumbWidth))) + position = (((position ?? 1) + draggedPositions) < 1) ? 1 : ((position ?? 1) + draggedPositions) } } + // Move the scrollbar thumb to the left while tapping on the left side of the scrubber. @objc func onThumbTouchStart(_ gesture: UILongPressGestureRecognizer) { if gesture.state == .began { leftActiveOverlay.backgroundColor = activeOverlayColorConfiguration.getColor(self) @@ -274,12 +279,11 @@ open class CarouselScrollbar: View { } else if gesture.state == .ended { leftActiveOverlay.backgroundColor = .clear leftActiveOverlay.layer.opacity = defaultOpacity - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in - self?.onMoveBackward() - } + self.onMoveBackward() } } + // Move the scrollbar thumb to the right while tapping on the right side of the scrubber. @objc func onThumbTouchEnd(_ gesture: UILongPressGestureRecognizer) { if gesture.state == .began { rightActiveOverlay.backgroundColor = activeOverlayColorConfiguration.getColor(self) @@ -290,12 +294,12 @@ open class CarouselScrollbar: View { } else if gesture.state == .ended { rightActiveOverlay.backgroundColor = .clear rightActiveOverlay.layer.opacity = defaultOpacity - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in - self?.onMoveForward() - } + self.onMoveForward() } } + // Minimum thumb width applied + // Incomplete set moves a shorter distance than the standard increment value. private func setThumbWidth() { let width = (Float(trackViewWidth) / Float(numberOfSlides ?? 1)) * Float(_selectedLayout.value) actualThumbWidth = (width > Float(trackViewWidth)) ? Float(trackViewWidth) : width @@ -306,6 +310,7 @@ open class CarouselScrollbar: View { updateActiveOverlays() } + // Update active overlay frames according to thumb position. private func updateActiveOverlays() { // adjusting thumb position if it goes beyond trackView. let thumbPosition = thumbView.frame.origin.x + thumbView.frame.size.width @@ -331,6 +336,7 @@ open class CarouselScrollbar: View { private func scrollThumbToPosition(_ position: Int?) { DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in self?.setThumb(at: position) + self?.onScrubberDidChange?(position ?? 1) } } From 5356650355200c20421f47cc4ca14b1acffb143f Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 21 Mar 2024 17:15:09 +0530 Subject: [PATCH 07/13] Digital ACT-191 ONEAPP-6830 story: increased hit area to thumb and track --- .../CarouselScrollbar/CarouselScrollbar.swift | 230 ++++++++++-------- 1 file changed, 124 insertions(+), 106 deletions(-) diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index 3671c27d..057668ef 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -122,7 +122,6 @@ open class CarouselScrollbar: View { internal var containerSize: CGSize { CGSize(width: 45, height: 44) } internal var _selectedLayout: Layout = .oneUP internal var _numberOfSlides: Int? = 1 - internal var heightConstraint: NSLayoutConstraint? internal var totalPositions: Int? = 1 internal var _position: Int? = 1 internal var trayOriginalCenter: CGPoint! @@ -131,41 +130,30 @@ open class CarouselScrollbar: View { private let trackViewHeight: CGFloat = 4 private let minThumbWidth: Float = 16.0 private var thumbWidth: Float = 16.0 - private var actualThumbWidth: Float = 0.0 + private var computedWidth: Float = 0.0 private let cornerRadius: CGFloat = 4.0 private let activeOpacity: Float = 0.15 private let defaultOpacity: Float = 1 - /// Track view with fixed width - internal var trackView: UIView = { - return UIView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - } - }() - - /// Left Active Track overlay with variable width - internal var leftActiveOverlay: UIView = { - return UIView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.tag = 2 - } - }() - - /// Right Active Track overlay with variable width - internal var rightActiveOverlay: UIView = { - return UIView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.tag = 3 - } - }() - - /// Thumb view with variable width - internal var thumbView: UIView = { - return UIView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - } - }() + internal var containerView = View().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + internal var trackView = View().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + internal var leftActiveOverlay = View().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + internal var rightActiveOverlay = View().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + internal var thumbView = View().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + internal var rightActiveOverlayLayer: CALayer = CALayer() + internal var leftActiveOverlayLayer: CALayer = CALayer() + internal var thumbViewLayer: CALayer = CALayer() //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- @@ -184,46 +172,67 @@ open class CarouselScrollbar: View { super.setup() accessibilityLabel = "Carousel Scrollbar" - //create the wrapping view - heightConstraint = self.heightAnchor.constraint(equalToConstant: containerSize.height) - heightConstraint?.priority = .defaultHigh - heightConstraint?.isActive = true + addSubview(containerView) + containerView + .pinTop() + .pinBottom() + .pinLeadingGreaterThanOrEqualTo() + .pinTrailingLessThanOrEqualTo() + .height(containerSize.height) + .width(CGFloat(trackViewWidth)) + + containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() //Trackview - trackView.frame = CGRectMake(20, 20, CGFloat(trackViewWidth), trackViewHeight) + trackView.frame = CGRectMake(0, 20, CGFloat(trackViewWidth), trackViewHeight) trackView.layer.cornerRadius = cornerRadius - addSubview(trackView) + containerView.addSubview(trackView) ///Left active overlay - leftActiveOverlay.frame = CGRectMake(trackView.frame.origin.x, 20, CGFloat(trackViewWidth), trackViewHeight) + leftActiveOverlay.frame = CGRectMake(trackView.frame.origin.x, 0, CGFloat(trackViewWidth), containerSize.height) leftActiveOverlay.isUserInteractionEnabled = true - leftActiveOverlay.layer.cornerRadius = cornerRadius + leftActiveOverlay.backgroundColor = .clear +// leftActiveOverlay.clipsToBounds = true let leftPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.onThumbTouchStart(_:))) leftPressRecognizer.minimumPressDuration = 0 leftActiveOverlay.addGestureRecognizer(leftPressRecognizer) - addSubview(leftActiveOverlay) + containerView.addSubview(leftActiveOverlay) + + leftActiveOverlay.layer.addSublayer(leftActiveOverlayLayer) + leftActiveOverlayLayer.cornerRadius = cornerRadius + leftActiveOverlayLayer.frame = .init(origin: .zero, size: .init(width: leftActiveOverlayLayer.frame.size.width, height: trackViewHeight)) ///Right active overlay - rightActiveOverlay.frame = CGRectMake(thumbView.frame.origin.x + thumbView.frame.size.width, 20, CGFloat(trackViewWidth), trackViewHeight) + rightActiveOverlay.frame = CGRectMake(thumbView.frame.origin.x + thumbView.frame.size.width, 0, CGFloat(trackViewWidth), containerSize.height) rightActiveOverlay.isUserInteractionEnabled = true - rightActiveOverlay.layer.cornerRadius = cornerRadius +// rightActiveOverlay.clipsToBounds = true + rightActiveOverlay.backgroundColor = .clear let rightPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.onThumbTouchEnd(_:))) rightPressRecognizer.minimumPressDuration = 0 rightActiveOverlay.addGestureRecognizer(rightPressRecognizer) - addSubview(rightActiveOverlay) + containerView.addSubview(rightActiveOverlay) + + rightActiveOverlay.layer.addSublayer(rightActiveOverlayLayer) + rightActiveOverlayLayer.cornerRadius = cornerRadius + rightActiveOverlayLayer.frame = .init(origin: .zero, size: .init(width: rightActiveOverlay.frame.size.width, height: trackViewHeight)) //Thumbview - thumbView.frame = CGRectMake(20, 20, CGFloat(thumbWidth), trackViewHeight) - thumbView.layer.cornerRadius = cornerRadius + thumbView.frame = CGRectMake(0, 0, CGFloat(thumbWidth), containerSize.height) + thumbView.backgroundColor = .clear thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(self.onScrubberDrag(_:))))) - addSubview(thumbView) + containerView.addSubview(thumbView) updateActiveOverlays() + + thumbViewLayer.cornerRadius = cornerRadius + thumbViewLayer.backgroundColor = thumbColorConfiguration.getColor(surface).cgColor + thumbViewLayer.frame = .init(origin: .init(x: 0, y: 20), size: .init(width: CGFloat(thumbWidth), height: trackViewHeight)) + thumbView.layer.addSublayer(thumbViewLayer) } open override func updateView() { super.updateView() trackView.backgroundColor = trackColorConfiguration.getColor(surface) - thumbView.backgroundColor = thumbColorConfiguration.getColor(surface) + thumbViewLayer.backgroundColor = thumbColorConfiguration.getColor(surface).cgColor } open override func updateAccessibility() { @@ -252,67 +261,22 @@ open class CarouselScrollbar: View { scrollThumbToPosition(position) } - // Drag scrollbar thumb to move it to the left or right. - // Upon releases of drag, the scrollbar thumb snaps to the closest full position and the scrollbar returns to original size without delay. - @objc func onScrubberDrag(_ sender: UIPanGestureRecognizer) { - let translation = sender.translation(in: thumbView) - if sender.state == UIGestureRecognizer.State.began { - trayOriginalCenter = thumbView.center - } else if sender.state == UIGestureRecognizer.State.changed { - let draggedPositions = Int (ceil (Double(translation.x) / Double(actualThumbWidth))) - setThumb(at: (position ?? 1) + draggedPositions) - } - else if sender.state == UIGestureRecognizer.State.cancelled || sender.state == UIGestureRecognizer.State.ended { - let draggedPositions = Int (ceil (Double(translation.x) / Double(actualThumbWidth))) - position = (((position ?? 1) + draggedPositions) < 1) ? 1 : ((position ?? 1) + draggedPositions) - } - } - - // Move the scrollbar thumb to the left while tapping on the left side of the scrubber. - @objc func onThumbTouchStart(_ gesture: UILongPressGestureRecognizer) { - if gesture.state == .began { - leftActiveOverlay.backgroundColor = activeOverlayColorConfiguration.getColor(self) - leftActiveOverlay.layer.opacity = activeOpacity - } else if gesture.state == .cancelled { - leftActiveOverlay.backgroundColor = .clear - leftActiveOverlay.layer.opacity = defaultOpacity - } else if gesture.state == .ended { - leftActiveOverlay.backgroundColor = .clear - leftActiveOverlay.layer.opacity = defaultOpacity - self.onMoveBackward() - } - } - - // Move the scrollbar thumb to the right while tapping on the right side of the scrubber. - @objc func onThumbTouchEnd(_ gesture: UILongPressGestureRecognizer) { - if gesture.state == .began { - rightActiveOverlay.backgroundColor = activeOverlayColorConfiguration.getColor(self) - rightActiveOverlay.layer.opacity = activeOpacity - } else if gesture.state == .cancelled { - rightActiveOverlay.backgroundColor = .clear - rightActiveOverlay.layer.opacity = defaultOpacity - } else if gesture.state == .ended { - rightActiveOverlay.backgroundColor = .clear - rightActiveOverlay.layer.opacity = defaultOpacity - self.onMoveForward() - } - } - - // Minimum thumb width applied - // Incomplete set moves a shorter distance than the standard increment value. + // Compute track width and should maintain minimum thumb width if needed private func setThumbWidth() { let width = (Float(trackViewWidth) / Float(numberOfSlides ?? 1)) * Float(_selectedLayout.value) - actualThumbWidth = (width > Float(trackViewWidth)) ? Float(trackViewWidth) : width + computedWidth = (width > Float(trackViewWidth)) ? Float(trackViewWidth) : width thumbWidth = (width <= Float(trackViewWidth) && width > minThumbWidth) ? width : ((width > Float(trackViewWidth)) ? Float(trackViewWidth) : minThumbWidth) thumbView.frame.size.width = CGFloat(thumbWidth) thumbView.frame.origin.x = trackView.frame.origin.x + thumbViewLayer.frame.size.width = thumbView.frame.size.width checkPositions() updateActiveOverlays() } + // Incomplete set moves a shorter distance than the standard increment value. // Update active overlay frames according to thumb position. private func updateActiveOverlays() { - // adjusting thumb position if it goes beyond trackView. + // adjusting thumb position if it goes beyond trackView on left/right. let thumbPosition = thumbView.frame.origin.x + thumbView.frame.size.width let trackPosition = trackView.frame.origin.x + trackView.frame.size.width if thumbPosition > trackPosition { @@ -323,26 +287,80 @@ open class CarouselScrollbar: View { //left active overlay position update leftActiveOverlay.frame.size.width = thumbView.frame.origin.x - trackView.frame.origin.x + cornerRadius - - //left active overlay position update + leftActiveOverlayLayer.frame = .init(origin: .init(x: 0, y: 20), size: .init(width: leftActiveOverlay.frame.size.width, height: trackViewHeight)) + + //right active overlay position update rightActiveOverlay.frame.origin.x = thumbView.frame.origin.x + thumbView.frame.size.width - cornerRadius rightActiveOverlay.frame.size.width = (trackView.frame.origin.x + trackView.frame.size.width) - (thumbView.frame.origin.x + thumbView.frame.size.width) + cornerRadius + rightActiveOverlayLayer.frame = .init(origin: .init(x: 0, y: 20), size: .init(width: rightActiveOverlay.frame.size.width, height: trackViewHeight)) + } + + // Drag scrollbar thumb to move it to the left or right. + // Upon releases of drag, the scrollbar thumb snaps to the closest full position and the scrollbar returns to original size without delay. + @objc func onScrubberDrag(_ sender: UIPanGestureRecognizer) { + let translation = sender.translation(in: thumbView) + if sender.state == UIGestureRecognizer.State.began { + trayOriginalCenter = thumbView.center + } else if sender.state == UIGestureRecognizer.State.changed { + let draggedPositions = Int (ceil (Double(translation.x) / Double(computedWidth))) + setThumb(at: (position ?? 1) + draggedPositions) + } + else if sender.state == UIGestureRecognizer.State.cancelled || sender.state == UIGestureRecognizer.State.ended { + let draggedPositions = Int (ceil (Double(translation.x) / Double(computedWidth))) + position = (((position ?? 1) + draggedPositions) < 1) ? 1 : ((position ?? 1) + draggedPositions) + } + } + + // Move the scrollbar thumb to the left while tapping on the left side of the scrubber. + @objc func onThumbTouchStart(_ gesture: UILongPressGestureRecognizer) { + UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { [self] in + if gesture.state == .began { + leftActiveOverlayLayer.backgroundColor = activeOverlayColorConfiguration.getColor(self).cgColor + leftActiveOverlayLayer.opacity = activeOpacity + } else if gesture.state == .cancelled { + leftActiveOverlayLayer.backgroundColor = UIColor.clear.cgColor + leftActiveOverlayLayer.opacity = defaultOpacity + } else if gesture.state == .ended { + leftActiveOverlayLayer.backgroundColor = UIColor.clear.cgColor + leftActiveOverlayLayer.opacity = defaultOpacity + UIView.performWithoutAnimation { + self.onMoveBackward() + } + } + }) + } + + // Move the scrollbar thumb to the right while tapping on the right side of the scrubber. + @objc func onThumbTouchEnd(_ gesture: UILongPressGestureRecognizer) { + UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { [self] in + if gesture.state == .began { + rightActiveOverlayLayer.backgroundColor = activeOverlayColorConfiguration.getColor(self).cgColor + rightActiveOverlayLayer.opacity = activeOpacity + } else if gesture.state == .cancelled { + rightActiveOverlayLayer.backgroundColor = UIColor.clear.cgColor + rightActiveOverlayLayer.opacity = defaultOpacity + } else if gesture.state == .ended { + rightActiveOverlayLayer.backgroundColor = UIColor.clear.cgColor + rightActiveOverlayLayer.opacity = defaultOpacity + self.onMoveForward() + } + }) } private func checkPositions() { totalPositions = Int (ceil (Double(numberOfSlides ?? 1) / Double(_selectedLayout.value))) } - + private func scrollThumbToPosition(_ position: Int?) { - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in - self?.setThumb(at: position) - self?.onScrubberDidChange?(position ?? 1) - } + setThumb(at: position) + onScrubberDidChange?(position ?? 1) } private func setThumb(at position: Int?) { - thumbView.frame.origin.x = CGFloat(Float((position ?? 1) - 1) * actualThumbWidth) + trackView.frame.origin.x - updateActiveOverlays() + UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { + self.thumbView.frame.origin.x = CGFloat(Float((position ?? 1) - 1) * self.computedWidth) + self.trackView.frame.origin.x + self.updateActiveOverlays() + }) } } From 22d82fac041bae77682019bc5e6beb36353a03e7 Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 21 Mar 2024 17:17:01 +0530 Subject: [PATCH 08/13] Digital ACT-191 ONEAPP-6830 story: removed commented code --- VDS/Components/CarouselScrollbar/CarouselScrollbar.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index 057668ef..84c3739f 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -192,7 +192,6 @@ open class CarouselScrollbar: View { leftActiveOverlay.frame = CGRectMake(trackView.frame.origin.x, 0, CGFloat(trackViewWidth), containerSize.height) leftActiveOverlay.isUserInteractionEnabled = true leftActiveOverlay.backgroundColor = .clear -// leftActiveOverlay.clipsToBounds = true let leftPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.onThumbTouchStart(_:))) leftPressRecognizer.minimumPressDuration = 0 leftActiveOverlay.addGestureRecognizer(leftPressRecognizer) @@ -205,7 +204,6 @@ open class CarouselScrollbar: View { ///Right active overlay rightActiveOverlay.frame = CGRectMake(thumbView.frame.origin.x + thumbView.frame.size.width, 0, CGFloat(trackViewWidth), containerSize.height) rightActiveOverlay.isUserInteractionEnabled = true -// rightActiveOverlay.clipsToBounds = true rightActiveOverlay.backgroundColor = .clear let rightPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.onThumbTouchEnd(_:))) rightPressRecognizer.minimumPressDuration = 0 @@ -363,5 +361,3 @@ open class CarouselScrollbar: View { }) } } - - From 39a4c5040b2896ef36fbc0bbe66056ac7f56437d Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 21 Mar 2024 17:29:10 +0530 Subject: [PATCH 09/13] Digital ACT-191 ONEAPP-6830 story: minor changes --- .../CarouselScrollbar/CarouselScrollbar.swift | 55 ++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index 84c3739f..b0337120 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -135,21 +135,11 @@ open class CarouselScrollbar: View { private let activeOpacity: Float = 0.15 private let defaultOpacity: Float = 1 - internal var containerView = View().with { - $0.translatesAutoresizingMaskIntoConstraints = false - } - internal var trackView = View().with { - $0.translatesAutoresizingMaskIntoConstraints = false - } - internal var leftActiveOverlay = View().with { - $0.translatesAutoresizingMaskIntoConstraints = false - } - internal var rightActiveOverlay = View().with { - $0.translatesAutoresizingMaskIntoConstraints = false - } - internal var thumbView = View().with { - $0.translatesAutoresizingMaskIntoConstraints = false - } + internal var containerView = View() + internal var trackView = View() + internal var leftActiveOverlay = View() + internal var rightActiveOverlay = View() + internal var thumbView = View() internal var rightActiveOverlayLayer: CALayer = CALayer() internal var leftActiveOverlayLayer: CALayer = CALayer() @@ -293,6 +283,25 @@ open class CarouselScrollbar: View { rightActiveOverlayLayer.frame = .init(origin: .init(x: 0, y: 20), size: .init(width: rightActiveOverlay.frame.size.width, height: trackViewHeight)) } + private func checkPositions() { + totalPositions = Int (ceil (Double(numberOfSlides ?? 1) / Double(_selectedLayout.value))) + } + + private func scrollThumbToPosition(_ position: Int?) { + setThumb(at: position) + onScrubberDidChange?(position ?? 1) + } + + private func setThumb(at position: Int?) { + UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { + self.thumbView.frame.origin.x = CGFloat(Float((position ?? 1) - 1) * self.computedWidth) + self.trackView.frame.origin.x + self.updateActiveOverlays() + }) + } + + //-------------------------------------------------- + // MARK: - Gesture Methods + //-------------------------------------------------- // Drag scrollbar thumb to move it to the left or right. // Upon releases of drag, the scrollbar thumb snaps to the closest full position and the scrollbar returns to original size without delay. @objc func onScrubberDrag(_ sender: UIPanGestureRecognizer) { @@ -344,20 +353,4 @@ open class CarouselScrollbar: View { } }) } - - private func checkPositions() { - totalPositions = Int (ceil (Double(numberOfSlides ?? 1) / Double(_selectedLayout.value))) - } - - private func scrollThumbToPosition(_ position: Int?) { - setThumb(at: position) - onScrubberDidChange?(position ?? 1) - } - - private func setThumb(at position: Int?) { - UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { - self.thumbView.frame.origin.x = CGFloat(Float((position ?? 1) - 1) * self.computedWidth) + self.trackView.frame.origin.x - self.updateActiveOverlays() - }) - } } From 9290e40a2a9a9c9d3268670ec8ebc2209d7b7758 Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 21 Mar 2024 21:32:04 +0530 Subject: [PATCH 10/13] Digital ACT-191 ONEAPP-6830 story: refactored code --- .../CarouselScrollbar/CarouselScrollbar.swift | 93 ++++++++----------- 1 file changed, 39 insertions(+), 54 deletions(-) diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index b0337120..6a072ccd 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -35,14 +35,10 @@ open class CarouselScrollbar: View { // MARK: - Public Properties //-------------------------------------------------- /// Used to set total number of slides within carousel - open var numberOfSlides: Int? { + open var numberOfSlides: Int { get { return _numberOfSlides } set { - if let newValue { - _numberOfSlides = newValue - } else { - _numberOfSlides = 1 - } + _numberOfSlides = newValue setThumbWidth() scrollThumbToPosition(position) setNeedsUpdate() @@ -95,15 +91,11 @@ open class CarouselScrollbar: View { } /// Used to set the position of the thumb(scrubber). This is used when the carousel container changes position, it will align the position of thumb(scrubber). - open var position: Int? { + open var position: Int { get { return _position } set { - if let newValue { - checkPositions() - _position = (newValue > totalPositions ?? 1) ? totalPositions : newValue - } else { - _position = 1 - } + checkPositions() + _position = (newValue > totalPositions) ? totalPositions : newValue scrollThumbToPosition(position) setNeedsUpdate() } @@ -121,9 +113,9 @@ open class CarouselScrollbar: View { // Sizes are from InVision design specs. internal var containerSize: CGSize { CGSize(width: 45, height: 44) } internal var _selectedLayout: Layout = .oneUP - internal var _numberOfSlides: Int? = 1 - internal var totalPositions: Int? = 1 - internal var _position: Int? = 1 + internal var _numberOfSlides: Int = 1 + internal var totalPositions: Int = 1 + internal var _position: Int = 1 internal var trayOriginalCenter: CGPoint! private let trackViewWidth = 96 @@ -131,11 +123,13 @@ open class CarouselScrollbar: View { private let minThumbWidth: Float = 16.0 private var thumbWidth: Float = 16.0 private var computedWidth: Float = 0.0 - private let cornerRadius: CGFloat = 4.0 + private let cornerRadius: CGFloat = 2.0 private let activeOpacity: Float = 0.15 private let defaultOpacity: Float = 1 - internal var containerView = View() + internal var containerView = View().with { + $0.clipsToBounds = true + } internal var trackView = View() internal var leftActiveOverlay = View() internal var rightActiveOverlay = View() @@ -182,7 +176,7 @@ open class CarouselScrollbar: View { leftActiveOverlay.frame = CGRectMake(trackView.frame.origin.x, 0, CGFloat(trackViewWidth), containerSize.height) leftActiveOverlay.isUserInteractionEnabled = true leftActiveOverlay.backgroundColor = .clear - let leftPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.onThumbTouchStart(_:))) + let leftPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(onLeftViewLongPressRecognizer(_:))) leftPressRecognizer.minimumPressDuration = 0 leftActiveOverlay.addGestureRecognizer(leftPressRecognizer) containerView.addSubview(leftActiveOverlay) @@ -195,7 +189,7 @@ open class CarouselScrollbar: View { rightActiveOverlay.frame = CGRectMake(thumbView.frame.origin.x + thumbView.frame.size.width, 0, CGFloat(trackViewWidth), containerSize.height) rightActiveOverlay.isUserInteractionEnabled = true rightActiveOverlay.backgroundColor = .clear - let rightPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.onThumbTouchEnd(_:))) + let rightPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(onRightViewLongPressRecognizer(_:))) rightPressRecognizer.minimumPressDuration = 0 rightActiveOverlay.addGestureRecognizer(rightPressRecognizer) containerView.addSubview(rightActiveOverlay) @@ -207,7 +201,7 @@ open class CarouselScrollbar: View { //Thumbview thumbView.frame = CGRectMake(0, 0, CGFloat(thumbWidth), containerSize.height) thumbView.backgroundColor = .clear - thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(self.onScrubberDrag(_:))))) + thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(onScrubberDrag(_:))))) containerView.addSubview(thumbView) updateActiveOverlays() @@ -240,18 +234,18 @@ open class CarouselScrollbar: View { // MARK: - Private Methods //-------------------------------------------------- func onMoveBackward() { - position = (position ?? 1) - 1 + position = position - 1 scrollThumbToPosition(position) } func onMoveForward() { - position = (position ?? 1) + 1 + position = position + 1 scrollThumbToPosition(position) } // Compute track width and should maintain minimum thumb width if needed private func setThumbWidth() { - let width = (Float(trackViewWidth) / Float(numberOfSlides ?? 1)) * Float(_selectedLayout.value) + let width = (Float(trackViewWidth) / Float(numberOfSlides)) * Float(_selectedLayout.value) computedWidth = (width > Float(trackViewWidth)) ? Float(trackViewWidth) : width thumbWidth = (width <= Float(trackViewWidth) && width > minThumbWidth) ? width : ((width > Float(trackViewWidth)) ? Float(trackViewWidth) : minThumbWidth) thumbView.frame.size.width = CGFloat(thumbWidth) @@ -284,12 +278,12 @@ open class CarouselScrollbar: View { } private func checkPositions() { - totalPositions = Int (ceil (Double(numberOfSlides ?? 1) / Double(_selectedLayout.value))) + totalPositions = Int (ceil (Double(numberOfSlides) / Double(_selectedLayout.value))) } private func scrollThumbToPosition(_ position: Int?) { - setThumb(at: position) - onScrubberDidChange?(position ?? 1) + self.setThumb(at: position) + self.onScrubberDidChange?(position ?? 1) } private func setThumb(at position: Int?) { @@ -310,46 +304,37 @@ open class CarouselScrollbar: View { trayOriginalCenter = thumbView.center } else if sender.state == UIGestureRecognizer.State.changed { let draggedPositions = Int (ceil (Double(translation.x) / Double(computedWidth))) - setThumb(at: (position ?? 1) + draggedPositions) + setThumb(at: position + draggedPositions) } else if sender.state == UIGestureRecognizer.State.cancelled || sender.state == UIGestureRecognizer.State.ended { let draggedPositions = Int (ceil (Double(translation.x) / Double(computedWidth))) - position = (((position ?? 1) + draggedPositions) < 1) ? 1 : ((position ?? 1) + draggedPositions) + position = ((position + draggedPositions) < 1) ? 1 : (position + draggedPositions) } } // Move the scrollbar thumb to the left while tapping on the left side of the scrubber. - @objc func onThumbTouchStart(_ gesture: UILongPressGestureRecognizer) { - UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { [self] in - if gesture.state == .began { - leftActiveOverlayLayer.backgroundColor = activeOverlayColorConfiguration.getColor(self).cgColor - leftActiveOverlayLayer.opacity = activeOpacity - } else if gesture.state == .cancelled { - leftActiveOverlayLayer.backgroundColor = UIColor.clear.cgColor - leftActiveOverlayLayer.opacity = defaultOpacity - } else if gesture.state == .ended { - leftActiveOverlayLayer.backgroundColor = UIColor.clear.cgColor - leftActiveOverlayLayer.opacity = defaultOpacity - UIView.performWithoutAnimation { - self.onMoveBackward() - } - } - }) + @objc func onLeftViewLongPressRecognizer(_ gesture: UILongPressGestureRecognizer) { + animateOverlay(layer: leftActiveOverlayLayer, with: gesture, onGestureEnd: onMoveBackward) } // Move the scrollbar thumb to the right while tapping on the right side of the scrubber. - @objc func onThumbTouchEnd(_ gesture: UILongPressGestureRecognizer) { - UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { [self] in + @objc func onRightViewLongPressRecognizer(_ gesture: UILongPressGestureRecognizer) { + animateOverlay(layer: rightActiveOverlayLayer, with: gesture, onGestureEnd: onMoveForward) + } + + private func animateOverlay(layer: CALayer, with gesture: UILongPressGestureRecognizer, onGestureEnd: @escaping(() -> Void)) { + UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { [weak self] in + guard let self else { return } if gesture.state == .began { - rightActiveOverlayLayer.backgroundColor = activeOverlayColorConfiguration.getColor(self).cgColor - rightActiveOverlayLayer.opacity = activeOpacity + layer.backgroundColor = activeOverlayColorConfiguration.getColor(self).cgColor + layer.opacity = activeOpacity } else if gesture.state == .cancelled { - rightActiveOverlayLayer.backgroundColor = UIColor.clear.cgColor - rightActiveOverlayLayer.opacity = defaultOpacity + layer.backgroundColor = UIColor.clear.cgColor + layer.opacity = defaultOpacity } else if gesture.state == .ended { - rightActiveOverlayLayer.backgroundColor = UIColor.clear.cgColor - rightActiveOverlayLayer.opacity = defaultOpacity - self.onMoveForward() + layer.backgroundColor = UIColor.clear.cgColor + layer.opacity = defaultOpacity + onGestureEnd() } }) } From ea976372886d61d40d63f7d769779da466b63448 Mon Sep 17 00:00:00 2001 From: vasavk Date: Tue, 26 Mar 2024 08:19:16 +0530 Subject: [PATCH 11/13] =?UTF-8?q?Digital=20ACT-191=20ONEAPP-6830=20story:?= =?UTF-8?q?=20added=20=E2=80=98CarouselScrollbar=E2=80=99=20in=20VDS.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VDS/VDS.docc/VDS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/VDS/VDS.docc/VDS.md b/VDS/VDS.docc/VDS.md index 8beb9255..f093317c 100755 --- a/VDS/VDS.docc/VDS.md +++ b/VDS/VDS.docc/VDS.md @@ -24,6 +24,7 @@ Using the system allows designers and developers to collaborate more easily and - ``Button`` - ``ButtonIcon`` - ``ButtonGroup`` +- ``CarouselScrollbar`` - ``Checkbox`` - ``CheckboxItem`` - ``CheckboxGroup`` From 6380026eaa7fa4f6197bba425dbb3f8d2570e9eb Mon Sep 17 00:00:00 2001 From: vasavk Date: Wed, 27 Mar 2024 16:01:15 +0530 Subject: [PATCH 12/13] Digital ACT-191 ONEAPP-6830 story: updated with required callbacks --- .../CarouselScrollbar/CarouselScrollbar.swift | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index 6a072ccd..260f14e5 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -105,7 +105,20 @@ open class CarouselScrollbar: View { open var scrubberId: Int? { didSet { setNeedsUpdate() } } /// A callback when the scrubber position changes. Passes parameters (position). - open var onScrubberDidChange: ((Int) -> Void)? + open var onScrubberDrag: ((Int) -> Void)? + + /// A callback when the thumb move forward. Passes parameters (position). + open var onMoveForward: ((Int) -> Void)? + + /// A callback when the thumb move backward. Passes parameters (position). + open var onMoveBackward: ((Int) -> Void)? + + /// A callback when the thumb touch start. Passes parameters (position). + open var onThumbTouchStart: ((Int) -> Void)? + + /// A callback when the thumb touch end. Passes parameters (position). + open var onThumbTouchEnd: ((Int) -> Void)? + //-------------------------------------------------- // MARK: - Private Properties @@ -154,6 +167,7 @@ open class CarouselScrollbar: View { open override func setup() { super.setup() + isAccessibilityElement = true accessibilityLabel = "Carousel Scrollbar" addSubview(containerView) @@ -201,7 +215,7 @@ open class CarouselScrollbar: View { //Thumbview thumbView.frame = CGRectMake(0, 0, CGFloat(thumbWidth), containerSize.height) thumbView.backgroundColor = .clear - thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(onScrubberDrag(_:))))) + thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(onScrubberChange(_:))))) containerView.addSubview(thumbView) updateActiveOverlays() @@ -233,14 +247,16 @@ open class CarouselScrollbar: View { //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- - func onMoveBackward() { + func movePositionBackward() { position = position - 1 scrollThumbToPosition(position) + self.onMoveBackward?(position) } - func onMoveForward() { + func movePositionForward() { position = position + 1 scrollThumbToPosition(position) + self.onMoveForward?(position) } // Compute track width and should maintain minimum thumb width if needed @@ -281,14 +297,14 @@ open class CarouselScrollbar: View { totalPositions = Int (ceil (Double(numberOfSlides) / Double(_selectedLayout.value))) } - private func scrollThumbToPosition(_ position: Int?) { + private func scrollThumbToPosition(_ position: Int) { self.setThumb(at: position) - self.onScrubberDidChange?(position ?? 1) + self.onScrubberDrag?(position) } - private func setThumb(at position: Int?) { + private func setThumb(at position: Int) { UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { - self.thumbView.frame.origin.x = CGFloat(Float((position ?? 1) - 1) * self.computedWidth) + self.trackView.frame.origin.x + self.thumbView.frame.origin.x = CGFloat(Float((position) - 1) * self.computedWidth) + self.trackView.frame.origin.x self.updateActiveOverlays() }) } @@ -298,10 +314,11 @@ open class CarouselScrollbar: View { //-------------------------------------------------- // Drag scrollbar thumb to move it to the left or right. // Upon releases of drag, the scrollbar thumb snaps to the closest full position and the scrollbar returns to original size without delay. - @objc func onScrubberDrag(_ sender: UIPanGestureRecognizer) { + @objc func onScrubberChange(_ sender: UIPanGestureRecognizer) { let translation = sender.translation(in: thumbView) if sender.state == UIGestureRecognizer.State.began { trayOriginalCenter = thumbView.center + self.onThumbTouchStart?(position) } else if sender.state == UIGestureRecognizer.State.changed { let draggedPositions = Int (ceil (Double(translation.x) / Double(computedWidth))) setThumb(at: position + draggedPositions) @@ -309,17 +326,20 @@ open class CarouselScrollbar: View { else if sender.state == UIGestureRecognizer.State.cancelled || sender.state == UIGestureRecognizer.State.ended { let draggedPositions = Int (ceil (Double(translation.x) / Double(computedWidth))) position = ((position + draggedPositions) < 1) ? 1 : (position + draggedPositions) + if sender.state == UIGestureRecognizer.State.ended { + self.onThumbTouchEnd?(position) + } } } // Move the scrollbar thumb to the left while tapping on the left side of the scrubber. @objc func onLeftViewLongPressRecognizer(_ gesture: UILongPressGestureRecognizer) { - animateOverlay(layer: leftActiveOverlayLayer, with: gesture, onGestureEnd: onMoveBackward) + animateOverlay(layer: leftActiveOverlayLayer, with: gesture, onGestureEnd: movePositionBackward) } // Move the scrollbar thumb to the right while tapping on the right side of the scrubber. @objc func onRightViewLongPressRecognizer(_ gesture: UILongPressGestureRecognizer) { - animateOverlay(layer: rightActiveOverlayLayer, with: gesture, onGestureEnd: onMoveForward) + animateOverlay(layer: rightActiveOverlayLayer, with: gesture, onGestureEnd: movePositionForward) } private func animateOverlay(layer: CALayer, with gesture: UILongPressGestureRecognizer, onGestureEnd: @escaping(() -> Void)) { From c64caa7a9f8ffd629ccba3bb3516ac34e470268c Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 27 Mar 2024 15:24:40 -0500 Subject: [PATCH 13/13] added publishers/cancellables Signed-off-by: Matt Bruce --- .../CarouselScrollbar/CarouselScrollbar.swift | 101 +++++++++++++++--- 1 file changed, 88 insertions(+), 13 deletions(-) diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index 260f14e5..5f99c6b7 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -100,25 +100,99 @@ open class CarouselScrollbar: View { setNeedsUpdate() } } - + /// Allows a unique id to be passed into the thumb and track of the thumb(scrubber). open var scrubberId: Int? { didSet { setNeedsUpdate() } } /// A callback when the scrubber position changes. Passes parameters (position). - open var onScrubberDrag: ((Int) -> Void)? + open var onScrubberDrag: ((Int) -> Void)? { + get { nil } + set { + onScrubberDragCancellable?.cancel() + if let newValue { + onScrubberDragCancellable = onScrubberDragPublisher + .sink { c in + newValue(c) + } + } + } + } + + /// A publisher for when the scrubber position changes. Passes parameters (position). + open var onScrubberDragPublisher = PassthroughSubject() + private var onScrubberDragCancellable: AnyCancellable? /// A callback when the thumb move forward. Passes parameters (position). - open var onMoveForward: ((Int) -> Void)? + open var onMoveForward: ((Int) -> Void)? { + get { nil } + set { + onMoveForwardCancellable?.cancel() + if let newValue { + onMoveForwardCancellable = onMoveForwardPublisher + .sink { c in + newValue(c) + } + } + } + } + + /// A publisher for when the thumb move forward. Passes parameters (position). + open var onMoveForwardPublisher = PassthroughSubject() + private var onMoveForwardCancellable: AnyCancellable? /// A callback when the thumb move backward. Passes parameters (position). - open var onMoveBackward: ((Int) -> Void)? + open var onMoveBackward: ((Int) -> Void)? { + get { nil } + set { + onMoveBackwardCancellable?.cancel() + if let newValue { + onMoveBackwardCancellable = onMoveBackwardPublisher + .sink { c in + newValue(c) + } + } + } + } + + /// A publisher for when the thumb move backward. Passes parameters (position). + open var onMoveBackwardPublisher = PassthroughSubject() + private var onMoveBackwardCancellable: AnyCancellable? /// A callback when the thumb touch start. Passes parameters (position). - open var onThumbTouchStart: ((Int) -> Void)? + open var onThumbTouchStart: ((Int) -> Void)? { + get { nil } + set { + onThumbTouchStartCancellable?.cancel() + if let newValue { + onThumbTouchStartCancellable = onThumbTouchStartPublisher + .sink { c in + newValue(c) + } + } + } + } + + /// A publisher for when the thumb touch start. Passes parameters (position). + open var onThumbTouchStartPublisher = PassthroughSubject() + private var onThumbTouchStartCancellable: AnyCancellable? /// A callback when the thumb touch end. Passes parameters (position). - open var onThumbTouchEnd: ((Int) -> Void)? + open var onThumbTouchEnd: ((Int) -> Void)? { + get { nil } + set { + onThumbTouchEndCancellable?.cancel() + if let newValue { + onThumbTouchEndCancellable = onThumbTouchEndPublisher + .sink { c in + newValue(c) + } + } + } + } + /// A publisher for when the thumb touch end. Passes parameters (position). + open var onThumbTouchEndPublisher = PassthroughSubject() + private var onThumbTouchEndCancellable: AnyCancellable? //-------------------------------------------------- // MARK: - Private Properties @@ -250,13 +324,13 @@ open class CarouselScrollbar: View { func movePositionBackward() { position = position - 1 scrollThumbToPosition(position) - self.onMoveBackward?(position) + onMoveBackwardPublisher.send(position) } func movePositionForward() { position = position + 1 scrollThumbToPosition(position) - self.onMoveForward?(position) + onMoveForwardPublisher.send(position) } // Compute track width and should maintain minimum thumb width if needed @@ -298,12 +372,13 @@ open class CarouselScrollbar: View { } private func scrollThumbToPosition(_ position: Int) { - self.setThumb(at: position) - self.onScrubberDrag?(position) + setThumb(at: position) + onScrubberDragPublisher.send(position) } private func setThumb(at position: Int) { - UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { + UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { [weak self] in + guard let self else { return } self.thumbView.frame.origin.x = CGFloat(Float((position) - 1) * self.computedWidth) + self.trackView.frame.origin.x self.updateActiveOverlays() }) @@ -318,7 +393,7 @@ open class CarouselScrollbar: View { let translation = sender.translation(in: thumbView) if sender.state == UIGestureRecognizer.State.began { trayOriginalCenter = thumbView.center - self.onThumbTouchStart?(position) + onThumbTouchStartPublisher.send(position) } else if sender.state == UIGestureRecognizer.State.changed { let draggedPositions = Int (ceil (Double(translation.x) / Double(computedWidth))) setThumb(at: position + draggedPositions) @@ -327,7 +402,7 @@ open class CarouselScrollbar: View { let draggedPositions = Int (ceil (Double(translation.x) / Double(computedWidth))) position = ((position + draggedPositions) < 1) ? 1 : (position + draggedPositions) if sender.state == UIGestureRecognizer.State.ended { - self.onThumbTouchEnd?(position) + onThumbTouchEndPublisher.send(position) } } }