From 928db0f1fc3e5bb69bf698442d3fe962997125bd Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Wed, 17 Jul 2024 18:32:04 +0530 Subject: [PATCH] Digital ACT-191 ONEAPP-9311 story: changes about PR notes --- VDS/Components/Carousel/Carousel.swift | 193 +++++------------- .../Carousel/CarouselSlotItemModel.swift | 4 +- 2 files changed, 58 insertions(+), 139 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index a3cb1f1c..90b35588 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -78,14 +78,11 @@ open class Carousel: View { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- - /// Aspect-ratio options for tilelet in the carousel. If 'none' is passed, the tilelet will take the height of the tallest item in the carousel. - open var aspectRatio: Tilelet.AspectRatio = .none { didSet { setNeedsUpdate() } } - - /// Data used to render tilelets in the carousel. - open var data: [Any] = [] { didSet { setNeedsUpdate() } } + /// views used to render view in the carousel slots. + open var views: [UIView] = [] { didSet { setNeedsUpdate() } } /// If provided, width of slots will be rendered based on this value. If omitted, default widths are rendered. - open var width : Width? { + open var width: Width? { get { _width } set { if let newValue { @@ -110,19 +107,11 @@ open class Carousel: View { } /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. - open var gutter: Gutter { - get { return _gutter } - set { - _gutter = newValue - setNeedsUpdate() - } - } + open var gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX { didSet { setNeedsUpdate() } } /// The amount of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. - open var layout: CarouselScrollbar.Layout { - get { return _layout } - set { - _layout = newValue + open var layout: CarouselScrollbar.Layout = UIDevice.isIPad ? .threeUP : .oneUP { + didSet { carouselScrollBar.position = 0 setNeedsUpdate() } @@ -143,82 +132,29 @@ open class Carousel: View { } /// Config object for pagination. - open var pagination: CarouselPaginationModel { - get { return _pagination } - set { - _pagination = newValue - setNeedsUpdate() - } - } + open var pagination: CarouselPaginationModel = .init(kind: .lowContrast, floating: true) { didSet {setNeedsUpdate() } } /// If provided, will determine the conditions to render the pagination arrows. - open var paginationDisplay: PaginationDisplay { - get { return _paginationDisplay } - set { - _paginationDisplay = newValue - setNeedsUpdate() - } - } + open var paginationDisplay: PaginationDisplay = .none { didSet {setNeedsUpdate() } } /// If provided, will apply margin to pagination arrows. Can be set to either positive or negative values. /// The default value will be 12px in tablet and 8px in mobile. These values are the default in order to avoid overlapping content within the carousel. - open var paginationInset: CGFloat { - get { return _paginationInset } - set { - _paginationInset = newValue - updatePaginationInset() - } - } + open var paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X { didSet { updatePaginationInset() } } /// Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices. - open var peek: Peek { - get { return _peek } - set { - _peek = newValue - setNeedsUpdate() - } - } + open var peek: Peek = .standard { didSet { setNeedsUpdate() } } /// The initial visible slide's index in the carousel. open var selectedIndex: Int? { didSet { setNeedsUpdate() } } /// If provided, will set the alignment for slot content when the slots has different heights. - open var slotAlignment: CarouselSlotAlignmentModel? { - get { return _slotAlignment } - set { - if let newValue { - _slotAlignment = newValue - } else { - _slotAlignment = nil - } - setNeedsUpdate() - } - } + open var slotAlignment: CarouselSlotAlignmentModel? = nil { didSet { setNeedsUpdate() } } /// Render item style. If provided, the slot gets the background, width, height, border-radius. - open var renderItemStyle: CarouselRenderItemStyle? { - get { return _renderItemStyle } - set { - if let newValue { - _renderItemStyle = newValue - } else { - _renderItemStyle = nil - } - setNeedsUpdate() - } - } + open var renderItemStyle: CarouselRenderItemStyle? = nil { didSet { setNeedsUpdate() } } - /// Render item. It passes a data array object and expects the styled component to apply in return. - open var renderItem: CarouselSlotItemModel? { - get { _renderItem } - set { - if let newValue { - _renderItem = newValue - } else { - _renderItem = nil - } - } - } +// /// Render item. It passes a data array object and expects the styled component to apply in return. +// open var renderItem: CarouselSlotItemModel? = nil { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Private Properties @@ -278,18 +214,6 @@ open class Carousel: View { /// A publisher for when the carousel moves. Passes parameters (data). open var onScrollPublisher = PassthroughSubject, Never>() private var onScrollCancellable: AnyCancellable? - - internal var _layout: CarouselScrollbar.Layout = UIDevice.isIPad ? .threeUP : .oneUP - internal var _pagination: CarouselPaginationModel = .init(kind: .lowContrast, floating: true) - internal var _paginationDisplay: PaginationDisplay = .none - internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X - internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX - internal var _peek: Peek = .standard - internal var _numberOfSlides: Int = 1 - internal var _slotAlignment: CarouselSlotAlignmentModel? = nil - internal var _renderItemStyle: CarouselRenderItemStyle? = nil - internal var _renderItem: CarouselSlotItemModel? = nil - private var _width: Width? = nil private var selectedGroupIndex: Int? = nil private var containerStackHeightConstraint: NSLayoutConstraint? @@ -301,7 +225,7 @@ open class Carousel: View { // 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 - var slotHeight = 100.0 + var slotHeight = 50.0 var peekMinimum = 24.0 var minimumSlotWidth = 0.0 var carouselScrollbarMinWidth = 96.0 @@ -386,8 +310,8 @@ open class Carousel: View { } } - carouselScrollBar.numberOfSlides = data.count - carouselScrollBar.layout = _layout + carouselScrollBar.numberOfSlides = views.count + carouselScrollBar.layout = layout if (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) { carouselScrollBar.position = 1 } @@ -417,7 +341,6 @@ open class Carousel: View { open override func reset() { super.reset() shouldUpdateView = false - aspectRatio = .none layout = UIDevice.isIPad ? .threeUP : .oneUP pagination = .init(kind: .lowContrast, floating: true) paginationDisplay = .none @@ -473,7 +396,7 @@ open class Carousel: View { // Show/Hide pagination buttons of Carousel based on First or Middle or Last private func showPaginationControls() { - if carouselScrollBar.numberOfSlides == _layout.value { + if carouselScrollBar.numberOfSlides == layout.value { previousButton.isHidden = true nextButton.isHidden = true } else { @@ -482,6 +405,26 @@ open class Carousel: View { } } + private func estimateHeightFor(item: CarouselSlotItemModel, with width: CGFloat) -> CGFloat { + let itemWidth = width + let maxSize = CGSize(width: itemWidth, height: CGFloat.greatestFiniteMagnitude) + let estItemSize = item.component?.systemLayoutSizeFitting(maxSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) ?? CGSize(width: itemWidth, height: item.defaultHeight) + return estItemSize.height + } + + private func fetchCarouselHeight() { + if views.count > 0 { + var height = slotHeight + for x in 0...views.count - 1 { + // Add received component + let item : CarouselSlotItemModel = .init(style: renderItemStyle, component: views[x]) + slotHeight = estimateHeightFor(item: item, with: minimumSlotWidth) + height = slotHeight > height ? slotHeight : height + } + slotHeight = height + } + } + // Add carousel slots and load data if any private func addCarouselSlots() { getSlotWidth() @@ -494,11 +437,13 @@ open class Carousel: View { // Removing subView from its parent view subView.removeFromSuperview() } + + fetchCarouselHeight() // Add carousel items - if data.count > 0 { + if views.count > 0 { var xPos = 0.0 - for x in 0...data.count - 1 { + for x in 0...views.count - 1 { // Add Carousel Slot let carouselSlot = View().with { @@ -507,8 +452,7 @@ open class Carousel: View { } scrollView.addSubview(carouselSlot) scrollView.delegate = self - let size = ratioSize(for: minimumSlotWidth) - slotHeight = size.height + carouselSlot .pinTop() .pinBottom() @@ -518,24 +462,28 @@ open class Carousel: View { carouselSlot.layer.cornerRadius = 12.0 xPos = xPos + minimumSlotWidth + gutter.value + // Add received component + let item : CarouselSlotItemModel = .init(style: renderItemStyle, component: views[x]) + let contentViewHeight = estimateHeightFor(item: item, with: minimumSlotWidth) + // Add subview for content to Carousel Slot let contentView = View().with { $0.clipsToBounds = true - $0.backgroundColor = UIColor(red: CGFloat(216) / 255.0, green: CGFloat(218) / 255.0, blue: CGFloat(218) / 255.0, alpha: 1) } carouselSlot.addSubview(contentView) - - // Add received component - let item : CarouselSlotItemModel = .init(style: renderItemStyle, component: data[x] as? UIView) + if let component = item.component { if slotAlignment != nil { + // If slotAlignment exist, should use expected height + contentView.widthAnchor.constraint(equalToConstant: minimumSlotWidth).activate() + contentView.heightAnchor.constraint(equalToConstant: contentViewHeight).activate() setSlotAlignment(contentView: contentView, parentView: carouselSlot) } else { +// // If no slotAlignment, should use full slot contentView.pinToSuperView() } contentView.addSubview(component) component.pinToSuperView() - contentView.layer.cornerRadius = component.layer.cornerRadius if var surfacedView = component as? Surfaceable { surfacedView.surface = surface } @@ -590,7 +538,7 @@ open class Carousel: View { // Get the slot width relative to the peak private func getSlotWidth() { let actualWidth = containerView.frame.size.width - let isScrollbarSuppressed = data.count > 0 && layout.value == data.count + let isScrollbarSuppressed = views.count > 0 && layout.value == views.count let isPeekMinimumOnTablet = UIDevice.isIPad && peek == .minimum let isPeekNone: Bool = peek == .none minimumSlotWidth = isScrollbarSuppressed || isPeekMinimumOnTablet || isPeekNone ? actualWidth - ((CGFloat(layout.value)-1) * gutter.value): actualWidth - (CGFloat(layout.value) * gutter.value) @@ -626,37 +574,6 @@ open class Carousel: View { updateScrollPosition(position: carouselScrollBar.position, callbackText:"pageControlClicks") } - // The size of slot depends on the selected aspect ratio - 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 @@ -688,7 +605,7 @@ open class Carousel: View { } else if position == totalPositions { xPos = scrollContentSizeWidth - containerView.frame.size.width } else { - let isScrollbarSuppressed = data.count > 0 && layout.value == data.count + let isScrollbarSuppressed = views.count > 0 && layout.value == views.count let isPeekMinimumOnTablet = UIDevice.isIPad && peek == .minimum if !isScrollbarSuppressed { let subpart = minimumSlotWidth + gutter.value @@ -719,7 +636,7 @@ open class Carousel: View { // Get the overall positions of the carousel scrollbar relative to the slides and selected layout private func totalPositions() -> Int { - return Int (ceil (Double(carouselScrollBar.numberOfSlides) / Double(_layout.value))) + return Int (ceil (Double(carouselScrollBar.numberOfSlides) / Double(layout.value))) } } diff --git a/VDS/Components/Carousel/CarouselSlotItemModel.swift b/VDS/Components/Carousel/CarouselSlotItemModel.swift index 3830b29f..a52192c4 100644 --- a/VDS/Components/Carousel/CarouselSlotItemModel.swift +++ b/VDS/Components/Carousel/CarouselSlotItemModel.swift @@ -14,7 +14,9 @@ public struct CarouselSlotItemModel { /// Style props if provided any public var style: CarouselRenderItemStyle? - + + public let defaultHeight: CGFloat = 50.0 + /// Component to be show on Carousel slot public var component: UIView?