From 8619c641096645cf2b80bf8a288cecb93030074b Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Tue, 2 Jul 2024 14:47:20 +0530 Subject: [PATCH] Digital ACT-191 ONEAPP-7013 story: refactored code --- VDS/Components/Carousel/Carousel.swift | 247 +++++++++++++------------ 1 file changed, 126 insertions(+), 121 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 498e1dc5..dbc10d98 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -183,13 +183,11 @@ open class Carousel: View { /// If provided, will set the alignment for slot content when the slots has different heights. open var slotAlignment: [CarouselSlotAlignmentModel] = [] { didSet { setNeedsUpdate() } } - + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - // Sizes are from InVision design specs. - internal var containerSize: CGSize { CGSize(width: 320, height: 44) } - + internal var containerSize: CGSize { CGSize(width: frame.size.width, height: 44) } private let contentStackView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical @@ -260,7 +258,7 @@ open class Carousel: View { private var prevButtonLeadingConstraint: NSLayoutConstraint? private var nextButtonTrailingConstraint: NSLayoutConstraint? private var containerLeadingConstraint: NSLayoutConstraint? - + // The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile. let scrollbarTopSpace = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X @@ -268,18 +266,21 @@ open class Carousel: View { var peekMinimum = 24.0 var minimumSlotWidth = 0.0 var carouselScrollbarMinWidth = 96.0 + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- + /// Executed on initialization for this View. open override func initialSetup() { super.initialSetup() } + /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() isAccessibilityElement = false - // add containerView + // Add containerView addSubview(containerView) containerView .pinTop() @@ -287,18 +288,19 @@ open class Carousel: View { .pinLeadingGreaterThanOrEqualTo() .pinTrailing() .heightGreaterThanEqualTo(containerSize.height) + containerView.centerYAnchor.constraint(equalTo: centerYAnchor).activate() containerLeadingConstraint = containerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 0) containerLeadingConstraint?.activate() - - // add content stackview + + // Add content stackview containerView.addSubview(contentStackView) - // add scrollview + // Add scrollview scrollContainerView.addSubview(scrollView) scrollView.pinToSuperView() - // add pagination button icons + // Add pagination button icons scrollContainerView.addSubview(previousButton) previousButton .pinLeadingGreaterThanOrEqualTo() @@ -309,7 +311,7 @@ open class Carousel: View { .pinTrailingLessThanOrEqualTo() .pinCenterY() - // add scroll container view & carousel scrollbar + // Add scroll container view & carousel scrollbar contentStackView.addArrangedSubview(scrollContainerView) contentStackView.addArrangedSubview(carouselScrollBar) contentStackView.setCustomSpacing(scrollbarTopSpace, after: scrollContainerView) @@ -322,7 +324,8 @@ open class Carousel: View { addlisteners() } - + + /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() @@ -353,7 +356,7 @@ open class Carousel: View { if peek == .none { paginationDisplay = .persistent } - + // Minimum (Mobile only) Supported only on Mobile viewports. If a user passes Minimum for tablet carousel, the peek reverts to Standard. if UIDevice.isIPad && peek == .minimum { peek = .standard @@ -363,11 +366,12 @@ open class Carousel: View { if peek == .standard && !UIDevice.isIPad && layout != CarouselScrollbar.Layout.oneUP { peek = .minimum } - + updatePaginationControls() addCarouselSlots() } + /// Resets to default settings. open override func reset() { super.reset() shouldUpdateView = false @@ -384,19 +388,69 @@ open class Carousel: View { //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- + private func addlisteners() { + nextButton.onClick = { _ in self.nextButtonClick() } + previousButton.onClick = { _ in self.previousButtonClick() } + + /// Will be called when the thumb move forward. + carouselScrollBar.onMoveForward = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onMoveForward") + } + + /// Will be called when the thumb move backward. + carouselScrollBar.onMoveBackward = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onMoveBackward") + } + + /// Will be called when the thumb touch start. + carouselScrollBar.onThumbTouchStart = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onThumbTouchStart") + } + + /// Will be called when the thumb touch end. + carouselScrollBar.onThumbTouchEnd = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onThumbTouchEnd") + } + } + + private func updatePaginationControls() { + containerView.surface = surface + showPaginationControls() + previousButton.kind = pagination.kind + previousButton.floating = pagination.floating + nextButton.kind = pagination.kind + nextButton.floating = pagination.floating + previousButton.surface = surface + nextButton.surface = surface + } + + private func showPaginationControls() { + if carouselScrollBar.numberOfSlides == _layout.value { + previousButton.isHidden = true + nextButton.isHidden = true + } else { + previousButton.isHidden = (carouselScrollBar.position == 1) || (paginationDisplay == .none) + nextButton.isHidden = (carouselScrollBar.position == totalPositions()) || (paginationDisplay == .none) + } + } + private func addCarouselSlots() { getSlotWidth() if containerView.frame.size.width > 0 { containerViewHeightConstraint?.isActive = false containerStackHeightConstraint?.isActive = false - // perform a loop to iterate each subView + // Perform a loop to iterate each subView scrollView.subviews.forEach { subView in - // removing subView from its parent view + // Removing subView from its parent view subView.removeFromSuperview() } - // add carousel items + // Add carousel items if data.count > 0 { var xPos = 0.0 for x in 0...data.count - 1 { @@ -433,86 +487,7 @@ open class Carousel: View { } } - private func ratioSize(for width: CGFloat) -> CGSize { - var height: CGFloat = width - - switch aspectRatio { - case .ratio1x1: - break; - case .ratio3x4: - height = (4 / 3) * width - case .ratio4x3: - height = (3 / 4) * width - case .ratio2x3: - height = (3 / 2) * width - case .ratio3x2: - height = (2 / 3) * width - case .ratio9x16: - height = (16 / 9) * width - case .ratio16x9: - height = (9 / 16) * width - case .ratio1x2: - height = (2 / 1) * width - case .ratio2x1: - height = (1 / 2) * width - - default: - break - } - - return CGSize(width: width, height: height) - } - - func addlisteners() { - nextButton.onClick = { _ in self.nextButtonClick() } - previousButton.onClick = { _ in self.previousButtonClick() } - - /// will be called when the thumb move forward. - carouselScrollBar.onMoveForward = { [weak self] scrubberId in - guard let self else { return } - updateScrollPosition(position: scrubberId, callbackText:"onMoveForward") - } - - /// will be called when the thumb move backward. - carouselScrollBar.onMoveBackward = { [weak self] scrubberId in - guard let self else { return } - updateScrollPosition(position: scrubberId, callbackText:"onMoveBackward") - } - - /// will be called when the thumb touch start. - carouselScrollBar.onThumbTouchStart = { [weak self] scrubberId in - guard let self else { return } - updateScrollPosition(position: scrubberId, callbackText:"onThumbTouchStart") - } - - /// will be called when the thumb touch end. - carouselScrollBar.onThumbTouchEnd = { [weak self] scrubberId in - guard let self else { return } - updateScrollPosition(position: scrubberId, callbackText:"onThumbTouchEnd") - } - } - - func updatePaginationControls() { - containerView.surface = surface - showPaginationControls() - previousButton.kind = pagination.kind - previousButton.floating = pagination.floating - nextButton.kind = pagination.kind - nextButton.floating = pagination.floating - previousButton.surface = surface - nextButton.surface = surface - } - - func updatePaginationInset() { - prevButtonLeadingConstraint?.isActive = false - nextButtonTrailingConstraint?.isActive = false - prevButtonLeadingConstraint = previousButton.leadingAnchor.constraint(equalTo: scrollContainerView.leadingAnchor, constant: paginationInset) - nextButtonTrailingConstraint = nextButton.trailingAnchor.constraint(equalTo: scrollContainerView.trailingAnchor, constant: -paginationInset) - prevButtonLeadingConstraint?.isActive = true - nextButtonTrailingConstraint?.isActive = true - } - - func getSlotWidth() { + private func getSlotWidth() { let actualWidth = containerView.frame.size.width let isScrollbarSuppressed = data.count > 0 && layout.value == data.count let isPeekMinimumOnTablet = UIDevice.isIPad && peek == .minimum @@ -538,19 +513,69 @@ open class Carousel: View { minimumSlotWidth = minimumSlotWidth / CGFloat(layout.value) } - func nextButtonClick() { + private func nextButtonClick() { carouselScrollBar.position = carouselScrollBar.position+1 showPaginationControls() updateScrollPosition(position: carouselScrollBar.position, callbackText:"pageControlClicks") } - func previousButtonClick() { + private func previousButtonClick() { carouselScrollBar.position = carouselScrollBar.position-1 showPaginationControls() updateScrollPosition(position: carouselScrollBar.position, callbackText:"pageControlClicks") } - func updateScrollPosition(position: Int, callbackText: String) { + private func ratioSize(for width: CGFloat) -> CGSize { + var height: CGFloat = width + + switch aspectRatio { + case .ratio1x1: + break; + case .ratio3x4: + height = (4 / 3) * width + case .ratio4x3: + height = (3 / 4) * width + case .ratio2x3: + height = (3 / 2) * width + case .ratio3x2: + height = (2 / 3) * width + case .ratio9x16: + height = (16 / 9) * width + case .ratio16x9: + height = (9 / 16) * width + case .ratio1x2: + height = (2 / 1) * width + case .ratio2x1: + height = (1 / 2) * width + + default: + break + } + + return CGSize(width: width, height: height) + } + + private func updatePaginationInset() { + prevButtonLeadingConstraint?.isActive = false + nextButtonTrailingConstraint?.isActive = false + prevButtonLeadingConstraint = previousButton.leadingAnchor.constraint(equalTo: scrollContainerView.leadingAnchor, constant: paginationInset) + nextButtonTrailingConstraint = nextButton.trailingAnchor.constraint(equalTo: scrollContainerView.trailingAnchor, constant: -paginationInset) + prevButtonLeadingConstraint?.isActive = true + nextButtonTrailingConstraint?.isActive = true + } + + private func updateScrollbarPosition(targetContentOffsetXPos:CGFloat) { + let scrollContentSizeWidth = scrollView.contentSize.width + let totalPositions = totalPositions() + let layoutSpace = Int (floor( Double(scrollContentSizeWidth / Double(totalPositions)))) + let remindSpace = Int(targetContentOffsetXPos) % layoutSpace + var contentPos = (Int(targetContentOffsetXPos) / layoutSpace) + 1 + contentPos = remindSpace > layoutSpace/2 ? contentPos+1 : contentPos + carouselScrollBar.position = contentPos + updateScrollPosition(position: contentPos, callbackText: "ScrollViewMoved") + } + + private func updateScrollPosition(position: Int, callbackText: String) { if carouselScrollBar.numberOfSlides > 0 { let scrollContentSizeWidth = scrollView.contentSize.width let totalPositions = totalPositions() @@ -586,30 +611,9 @@ open class Carousel: View { } } - func updateScrollbarPosition(targetContentOffsetXPos:CGFloat) { - let scrollContentSizeWidth = scrollView.contentSize.width - let totalPositions = totalPositions() - let layoutSpace = Int (floor( Double(scrollContentSizeWidth / Double(totalPositions)))) - let remindSpace = Int(targetContentOffsetXPos) % layoutSpace - var contentPos = (Int(targetContentOffsetXPos) / layoutSpace) + 1 - contentPos = remindSpace > layoutSpace/2 ? contentPos+1 : contentPos - carouselScrollBar.position = contentPos - updateScrollPosition(position: contentPos, callbackText: "ScrollViewMoved") - } - private func totalPositions() -> Int { return Int (ceil (Double(carouselScrollBar.numberOfSlides) / Double(_layout.value))) } - - func showPaginationControls() { - if carouselScrollBar.numberOfSlides == _layout.value { - previousButton.isHidden = true - nextButton.isHidden = true - } else { - previousButton.isHidden = (carouselScrollBar.position == 1) || (paginationDisplay == .none) - nextButton.isHidden = (carouselScrollBar.position == totalPositions()) || (paginationDisplay == .none) - } - } } extension Carousel: UIScrollViewDelegate { @@ -619,6 +623,7 @@ extension Carousel: UIScrollViewDelegate { public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { updateScrollbarPosition(targetContentOffsetXPos: targetContentOffset.pointee.x) } + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { var visibleRect = CGRect() visibleRect.origin = scrollView.contentOffset