Digital ACT-191 ONEAPP-7013 story: show slides on Scrollview

This commit is contained in:
vasavk 2024-06-11 15:16:52 +05:30
parent 0fc8c5c530
commit 35e4cf0301
3 changed files with 186 additions and 108 deletions

View File

@ -30,6 +30,92 @@ open class Carousel: View {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Enums
//--------------------------------------------------
/// Enum used to describe the aspect ratios used for this component.
public enum AspectRatio: String, CaseIterable {
case ratio1x1 = "1:1"
case ratio3x4 = "3:4"
case ratio4x3 = "4:3"
case ratio2x3 = "2:3"
case ratio3x2 = "3:2"
case ratio9x16 = "9:16"
case ratio16x9 = "16:9"
case ratio1x2 = "1:2"
case ratio2x1 = "2:1"
case none
}
/// Enum used to describe the number of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile.
public enum Layout: String, CaseIterable {
case oneUP = "1UP"
case twoUP = "2UP"
case threeUP = "3UP"
case fourUP = "4UP"
case fiveUP = "5UP"
case sixUP = "6UP"
var value: Int {
switch self {
case .oneUP:
1
case .twoUP:
2
case .threeUP:
3
case .fourUP:
4
case .fiveUP:
5
case .sixUP:
6
}
}
}
/// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile.
public enum Gutter: String, CaseIterable {
case twelvePX = "12px"
case twentyFourPX = "24px"
var value: CGFloat {
switch self {
case .twelvePX:
VDSLayout.space3X
case .twentyFourPX:
VDSLayout.space6X
}
}
}
/// Enum used to describe the pagination display for this component.
public enum PaginationDisplay: String, CaseIterable {
case onHover, persistent, none
}
/// Enum used to describe the peek for this component. Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices.
public enum Peek: String, CaseIterable {
case standard, minimum, none
}
// TO DO: move to model class
/// Enum used to describe the vertical of slotAlignment.
public enum Vertical: String, CaseIterable {
case top, middle, bottom
}
/// Enum used to describe the horizontal of slotAlignment.
public enum Horizontal: String, CaseIterable {
case left, center, right
}
/// Enum used to describe the width of a fixed value or percentage of parent's width.
public enum Width {
case percentage(CGFloat)
case value(CGFloat)
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
@ -38,8 +124,30 @@ open class Carousel: View {
/// Data used to render tilelets in the carousel.
open var data: [Any] = [] { didSet { setNeedsUpdate() } }
/// If provided, width of slots will be rendered based on this value. If omitted, default widths are rendered.
open var width : Width? {
get { _width }
set {
if let newValue {
switch newValue {
case .percentage(let percentage):
if percentage >= 10 && percentage <= 100.0 {
_width = newValue
}
case .value(let value):
if value > minimumSlotWidth { /*(size.minimumSlotWidth)*/
_width = newValue
}
}
} else {
_width = nil
}
setNeedsUpdate()
}
}
/// Space between each tile. The default value will be 24px in tablet and 12px in mobile.
/// 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 {
@ -113,87 +221,7 @@ 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: - Enums
//--------------------------------------------------
/// Enum used to describe the aspect ratios used for this component.
public enum AspectRatio: String, CaseIterable {
case ratio1x1 = "1:1"
case ratio3x4 = "3:4"
case ratio4x3 = "4:3"
case ratio2x3 = "2:3"
case ratio3x2 = "3:2"
case ratio9x16 = "9:16"
case ratio16x9 = "16:9"
case ratio1x2 = "1:2"
case ratio2x1 = "2:1"
case none
}
/// Enum used to describe the number of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile.
public enum Layout: String, CaseIterable {
case oneUP = "1UP"
case twoUP = "2UP"
case threeUP = "3UP"
case fourUP = "4UP"
case fiveUP = "5UP"
case sixUP = "6UP"
var value: Int {
switch self {
case .oneUP:
1
case .twoUP:
2
case .threeUP:
3
case .fourUP:
4
case .fiveUP:
5
case .sixUP:
6
}
}
}
/// Enum used to describe the number of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile.
public enum Gutter: String, CaseIterable {
case twelvePX = "12px"
case twentyFourPX = "24px"
var value: CGFloat {
switch self {
case .twelvePX:
VDSLayout.space12X
case .twentyFourPX:
VDSLayout.space24X
}
}
}
/// Enum used to describe the pagination display for this component.
public enum PaginationDisplay: String, CaseIterable {
case onHover, persistent, none
}
/// Enum used to describe the peek for this component. Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices.
public enum Peek: String, CaseIterable {
case standard, minimum, none
}
// TO DO: move to model class
/// Enum used to describe the vertical of slotAlignment.
public enum Vertical: String, CaseIterable {
case top, middle, bottom
}
/// Enum used to describe the horizontal of slotAlignment.
public enum Horizontal: String, CaseIterable {
case left, center, right
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
@ -204,7 +232,7 @@ open class Carousel: View {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .vertical
$0.distribution = .fill
$0.spacing = UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X
$0.spacing = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X
$0.backgroundColor = .clear
}
@ -254,19 +282,21 @@ open class Carousel: View {
internal var _layout: 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.space12X : VDSLayout.space8X
internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X
internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX
internal var _peek: Peek = .none
internal var _numberOfSlides: Int = 1
private var _width: Width? = nil
private var selectedGroupIndex: Int? { didSet { setNeedsUpdate() } }
private var containerStackHeightConstraint: NSLayoutConstraint?
private var containerViewHeightConstraint: NSLayoutConstraint?
// The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile.
let space = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X
let scrollbarTopSpace = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X
var slotHeight = 100.0
var peekMinimum = 24.0
var minimumSlotWidth = 0.0
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
@ -293,6 +323,7 @@ open class Carousel: View {
// add scrollview
scrollContainerView.addSubview(scrollView)
scrollView.pinToSuperView()
// add pagination button icons
scrollContainerView.addSubview(previousButton)
@ -308,37 +339,84 @@ open class Carousel: View {
// add scroll container view & carousel scrollbar
contentStackView.addArrangedSubview(scrollContainerView)
contentStackView.addArrangedSubview(carouselScrollBar)
contentStackView.setCustomSpacing(space, after: scrollContainerView)
contentStackView.setCustomSpacing(scrollbarTopSpace, after: scrollContainerView)
contentStackView
.pinTop()
.pinBottom()
.pinLeading()
.pinTrailing()
.heightGreaterThanEqualTo(space+containerSize.height)
.heightGreaterThanEqualTo(scrollbarTopSpace + containerSize.height)
contentStackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate()
}
open override func updateView() {
containerViewHeightConstraint?.isActive = false
super.updateView()
containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: 200)
containerViewHeightConstraint?.isActive = true
containerViewHeightConstraint?.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 = gutter.value
for _ in 0...data.count - 1 {
let carouselSlot = View().with {
$0.clipsToBounds = true
$0.backgroundColor = .lightGray
}
scrollView.addSubview(carouselSlot)
carouselSlot
.pinTop()
.pinBottom()
.pinLeading(xPos)
.width(minimumSlotWidth)
.height(slotHeight)
xPos = xPos + minimumSlotWidth + gutter.value
}
scrollView.heightAnchor.constraint(equalToConstant: slotHeight).isActive = true
scrollView.contentSize = CGSize(width: xPos-minimumSlotWidth, height: slotHeight)
}
let containerHeight = slotHeight + scrollbarTopSpace + containerSize.height
containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: containerHeight)
containerViewHeightConstraint?.isActive = true
layoutIfNeeded()
}
open override func reset() {
super.reset()
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------
func updatePaginationControls() {
containerView.surface = surface
previousButton.isHidden = (paginationDisplay == .none)
nextButton.isHidden = (paginationDisplay == .none)
previousButton.kind = pagination.kind
previousButton.floating = pagination.floating
nextButton.kind = pagination.kind
nextButton.floating = pagination.floating
layoutIfNeeded()
previousButton.surface = surface
nextButton.surface = surface
}
open override func reset() {
// for subview in subviews {
// for recognizer in subview.gestureRecognizers ?? [] {
// subview.removeGestureRecognizer(recognizer)
// }
// }
super.reset()
func getSlotWidth() {
let actualWidth = containerView.frame.size.width
minimumSlotWidth = actualWidth - (CGFloat(layout.value) * gutter.value)
switch peek {
case .standard:
minimumSlotWidth = minimumSlotWidth - (3*peekMinimum)
case .minimum:
minimumSlotWidth = minimumSlotWidth - peekMinimum
case .none:
break
}
minimumSlotWidth = minimumSlotWidth / CGFloat(layout.value)
}
}

View File

@ -12,10 +12,10 @@ import UIKit
extension Carousel {
public struct CarouselPaginationModel {
/// Button icon property 'kind' is supported
/// Pagination supports Button icon property 'kind'.
public var kind: ButtonIcon.Kind
/// Button icon property 'floating' is supported
/// Pagination supports Button icon property 'floating'.
public var floating: Bool
public init(kind: ButtonIcon.Kind, floating: Bool) {

View File

@ -45,13 +45,13 @@ open class CarouselScrollbar: View {
}
/// The number of slides that can appear at once in a set in a carousel container.
open var selectedLayout: Layout? {
get { return _selectedLayout }
open var layout: Layout? {
get { return _layout }
set {
if let newValue {
_selectedLayout = newValue
_layout = newValue
} else {
_selectedLayout = .oneUP
_layout = .oneUP
}
setThumbWidth()
scrollThumbToPosition(position)
@ -198,7 +198,7 @@ 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 _layout: Layout = .oneUP
internal var _numberOfSlides: Int = 1
internal var totalPositions: Int = 1
internal var _position: Int = 1
@ -329,7 +329,7 @@ open class CarouselScrollbar: View {
// Compute track width and should maintain minimum thumb width if needed
private func setThumbWidth() {
let width = (Float(trackViewWidth) / Float(numberOfSlides)) * Float(_selectedLayout.value)
let width = (Float(trackViewWidth) / Float(numberOfSlides)) * Float(_layout.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)
@ -362,7 +362,7 @@ open class CarouselScrollbar: View {
}
private func checkPositions() {
totalPositions = Int (ceil (Double(numberOfSlides) / Double(_selectedLayout.value)))
totalPositions = Int (ceil (Double(numberOfSlides) / Double(_layout.value)))
}
private func scrollThumbToPosition(_ position: Int) {