Digital ACT-191 ONEAPP-7013 story: show slides on Scrollview
This commit is contained in:
parent
0fc8c5c530
commit
35e4cf0301
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user