diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index c5d0ab0e..5b5c99bf 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -79,7 +79,7 @@ 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 = .ratio1x1 { didSet { setNeedsUpdate() } } + open var aspectRatio: Tilelet.AspectRatio = .none { didSet { setNeedsUpdate() } } /// Data used to render tilelets in the carousel. open var data: [Any] = [] { didSet { setNeedsUpdate() } } @@ -92,10 +92,13 @@ open class Carousel: View { switch newValue { case .percentage(let percentage): if percentage >= 10 && percentage <= 100.0 { - _width = newValue + let expectedWidth = safeAreaLayoutGuide.layoutFrame.size.width * (percentage/100) + if expectedWidth > carouselScrollbarMinWidth { + _width = newValue + } } case .value(let value): - if value > minimumSlotWidth { /*(size.minimumSlotWidth)*/ + if value > carouselScrollbarMinWidth { _width = newValue } } @@ -256,6 +259,7 @@ open class Carousel: View { private var containerViewHeightConstraint: NSLayoutConstraint? 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 @@ -263,6 +267,7 @@ open class Carousel: View { var slotHeight = 100.0 var peekMinimum = 24.0 var minimumSlotWidth = 0.0 + var carouselScrollbarMinWidth = 96.0 //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -279,11 +284,12 @@ open class Carousel: View { containerView .pinTop() .pinBottom() - .pinLeading() + .pinLeadingGreaterThanOrEqualTo() .pinTrailing() .heightGreaterThanEqualTo(containerSize.height) - containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() - + containerView.centerYAnchor.constraint(equalTo: centerYAnchor).activate() + containerLeadingConstraint = containerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 0) + // add content stackview containerView.addSubview(contentStackView) @@ -312,13 +318,30 @@ open class Carousel: View { .pinLeading() .pinTrailing() .heightGreaterThanEqualTo(containerSize.height) - contentStackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() addlisteners() } open override func updateView() { super.updateView() + + if containerView.frame.size.width > 0 { + if let width { + containerLeadingConstraint?.deactivate() + switch width { + case .value(let value): + var expectedWidth = value + let fullWidth = safeAreaLayoutGuide.layoutFrame.size.width + expectedWidth = expectedWidth > fullWidth ? fullWidth : expectedWidth + containerLeadingConstraint?.constant = safeAreaLayoutGuide.layoutFrame.size.width - expectedWidth + case .percentage(let percentage): + let expectedWidth = safeAreaLayoutGuide.layoutFrame.size.width * (percentage/100) + containerLeadingConstraint?.constant = safeAreaLayoutGuide.layoutFrame.size.width - expectedWidth + } + containerLeadingConstraint?.activate() + } + } + carouselScrollBar.numberOfSlides = data.count carouselScrollBar.layout = _layout carouselScrollBar.position = (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) ? 1 : carouselScrollBar.position @@ -335,58 +358,59 @@ open class Carousel: View { peek = .standard } - containerViewHeightConstraint?.isActive = false - containerStackHeightConstraint?.isActive = false updatePaginationControls() getSlotWidth() - - // perform a loop to iterate each subView - scrollView.subviews.forEach { subView in - // removing subView from its parent view - subView.removeFromSuperview() - } - - // add carousel items - if data.count > 0 { - var xPos = 0.0 - for x in 0...data.count - 1 { - let carouselSlot = 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) - } - scrollView.addSubview(carouselSlot) - let size = ratioSize(for: minimumSlotWidth) - slotHeight = size.height - carouselSlot - .pinTop() - .pinBottom() - .pinLeading(xPos) - .width(minimumSlotWidth) - .height(slotHeight) - carouselSlot.layer.cornerRadius = 12.0 - xPos = xPos + minimumSlotWidth + gutter.value + + if containerView.frame.size.width > 0 { + containerViewHeightConstraint?.isActive = false + containerStackHeightConstraint?.isActive = false + + // perform a loop to iterate each subView + scrollView.subviews.forEach { subView in + // removing subView from its parent view + subView.removeFromSuperview() } - scrollView.contentSize = CGSize(width: xPos - gutter.value, height: slotHeight) + + // add carousel items + if data.count > 0 { + var xPos = 0.0 + for x in 0...data.count - 1 { + let carouselSlot = 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) + } + scrollView.addSubview(carouselSlot) + let size = ratioSize(for: minimumSlotWidth) + slotHeight = size.height + carouselSlot + .pinTop() + .pinBottom() + .pinLeading(xPos) + .width(minimumSlotWidth) + .height(slotHeight) + carouselSlot.layer.cornerRadius = 12.0 + xPos = xPos + minimumSlotWidth + gutter.value + } + scrollView.contentSize = CGSize(width: xPos - gutter.value, height: slotHeight) + } + + let containerHeight = slotHeight + scrollbarTopSpace + containerSize.height + if carouselScrollBar.isHidden { + containerStackHeightConstraint = contentStackView.heightAnchor.constraint(equalToConstant: slotHeight) + containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: slotHeight) + } else { + containerStackHeightConstraint = contentStackView.heightAnchor.constraint(equalToConstant: containerHeight) + containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: containerHeight) + } + containerViewHeightConstraint?.isActive = true + containerStackHeightConstraint?.isActive = true } - - let containerHeight = slotHeight + scrollbarTopSpace + containerSize.height - if carouselScrollBar.isHidden { - containerStackHeightConstraint = contentStackView.heightAnchor.constraint(equalToConstant: slotHeight) - containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: slotHeight) - } else { - containerStackHeightConstraint = contentStackView.heightAnchor.constraint(equalToConstant: containerHeight) - containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: containerHeight) - } - containerViewHeightConstraint?.isActive = true - containerStackHeightConstraint?.isActive = true - - layoutIfNeeded() } open override func reset() { super.reset() shouldUpdateView = false - aspectRatio = .ratio1x1 + aspectRatio = .none layout = UIDevice.isIPad ? .threeUP : .oneUP pagination = .init(kind: .lowContrast, floating: true) paginationDisplay = .none