Merge branch 'vasavk/carouselScrollbar' into 'develop'
VDS Brand 3.0 Carousel Scrollbar for IOS See merge request BPHV_MIPS/vds_ios!188
This commit is contained in:
commit
51c94b96cb
@ -10,6 +10,8 @@
|
|||||||
1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */; };
|
1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */; };
|
||||||
18450CF12BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */; };
|
18450CF12BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */; };
|
||||||
1855EC662BAABF2A002ACAC2 /* BreadcrumbItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */; };
|
1855EC662BAABF2A002ACAC2 /* BreadcrumbItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */; };
|
||||||
|
1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; };
|
||||||
|
1808BEC02BA456B700129230 /* CarouselScrollbarChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */; };
|
||||||
186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */; };
|
186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */; };
|
||||||
18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */; };
|
18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */; };
|
||||||
18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A012B96E848006602CC /* Breadcrumbs.swift */; };
|
18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A012B96E848006602CC /* Breadcrumbs.swift */; };
|
||||||
@ -197,6 +199,8 @@
|
|||||||
1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbCellItem.swift; sourceTree = "<group>"; };
|
1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbCellItem.swift; sourceTree = "<group>"; };
|
||||||
18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = BreadcrumbsChangeLog.txt; sourceTree = "<group>"; };
|
18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = BreadcrumbsChangeLog.txt; sourceTree = "<group>"; };
|
||||||
1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItemModel.swift; sourceTree = "<group>"; };
|
1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItemModel.swift; sourceTree = "<group>"; };
|
||||||
|
1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = "<group>"; };
|
||||||
|
1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = "<group>"; };
|
||||||
186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TextAreaChangeLog.txt; sourceTree = "<group>"; };
|
186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TextAreaChangeLog.txt; sourceTree = "<group>"; };
|
||||||
18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconBadgeIndicatorModel.swift; sourceTree = "<group>"; };
|
18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconBadgeIndicatorModel.swift; sourceTree = "<group>"; };
|
||||||
18A65A012B96E848006602CC /* Breadcrumbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Breadcrumbs.swift; sourceTree = "<group>"; };
|
18A65A012B96E848006602CC /* Breadcrumbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Breadcrumbs.swift; sourceTree = "<group>"; };
|
||||||
@ -406,6 +410,15 @@
|
|||||||
path = Breadcrumbs;
|
path = Breadcrumbs;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
1808BEBA2BA41B1D00129230 /* CarouselScrollbar */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */,
|
||||||
|
1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */,
|
||||||
|
);
|
||||||
|
path = CarouselScrollbar;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
445BA07629C07ABA0036A7C5 /* Notification */ = {
|
445BA07629C07ABA0036A7C5 /* Notification */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -554,6 +567,7 @@
|
|||||||
EAD062AE2A3B87210015965D /* BadgeIndicator */,
|
EAD062AE2A3B87210015965D /* BadgeIndicator */,
|
||||||
18A65A002B96E7E1006602CC /* Breadcrumbs */,
|
18A65A002B96E7E1006602CC /* Breadcrumbs */,
|
||||||
EA0FC2BE2912D18200DF80B4 /* Buttons */,
|
EA0FC2BE2912D18200DF80B4 /* Buttons */,
|
||||||
|
1808BEBA2BA41B1D00129230 /* CarouselScrollbar */,
|
||||||
EAF7F092289985E200B287F5 /* Checkbox */,
|
EAF7F092289985E200B287F5 /* Checkbox */,
|
||||||
EA985BF3296C609E00F2FF2E /* Icon */,
|
EA985BF3296C609E00F2FF2E /* Icon */,
|
||||||
EA3362412892EF700071C351 /* Label */,
|
EA3362412892EF700071C351 /* Label */,
|
||||||
@ -1033,6 +1047,7 @@
|
|||||||
EAEEECA22B1F92AD00531FC2 /* LabelChangeLog.txt in Resources */,
|
EAEEECA22B1F92AD00531FC2 /* LabelChangeLog.txt in Resources */,
|
||||||
EA3362072891E14D0071C351 /* VerizonNHGeDS-Regular.otf in Resources */,
|
EA3362072891E14D0071C351 /* VerizonNHGeDS-Regular.otf in Resources */,
|
||||||
EAEEEC9A2B1F8E4400531FC2 /* TextLinkChangeLog.txt in Resources */,
|
EAEEEC9A2B1F8E4400531FC2 /* TextLinkChangeLog.txt in Resources */,
|
||||||
|
1808BEC02BA456B700129230 /* CarouselScrollbarChangeLog.txt in Resources */,
|
||||||
EAEEECAF2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt in Resources */,
|
EAEEECAF2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt in Resources */,
|
||||||
EAEEEC922B1F807300531FC2 /* BadgeChangeLog.txt in Resources */,
|
EAEEEC922B1F807300531FC2 /* BadgeChangeLog.txt in Resources */,
|
||||||
EAEEEC9E2B1F8F7700531FC2 /* ButtonGroupChangeLog.txt in Resources */,
|
EAEEEC9E2B1F8F7700531FC2 /* ButtonGroupChangeLog.txt in Resources */,
|
||||||
@ -1162,6 +1177,7 @@
|
|||||||
EA3361AF288B26310071C351 /* FormFieldable.swift in Sources */,
|
EA3361AF288B26310071C351 /* FormFieldable.swift in Sources */,
|
||||||
EA513A952A4E1F82002A4DFF /* TitleLockupStyleConfiguration.swift in Sources */,
|
EA513A952A4E1F82002A4DFF /* TitleLockupStyleConfiguration.swift in Sources */,
|
||||||
44604AD729CE196600E62B51 /* Line.swift in Sources */,
|
44604AD729CE196600E62B51 /* Line.swift in Sources */,
|
||||||
|
1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */,
|
||||||
EAF978212A99035B00C2FEA9 /* Enabling.swift in Sources */,
|
EAF978212A99035B00C2FEA9 /* Enabling.swift in Sources */,
|
||||||
EA5E3058295105A40082B959 /* Tilelet.swift in Sources */,
|
EA5E3058295105A40082B959 /* Tilelet.swift in Sources */,
|
||||||
EA89201528B56CF4006B9984 /* RadioBoxGroup.swift in Sources */,
|
EA89201528B56CF4006B9984 /* RadioBoxGroup.swift in Sources */,
|
||||||
|
|||||||
436
VDS/Components/CarouselScrollbar/CarouselScrollbar.swift
Normal file
436
VDS/Components/CarouselScrollbar/CarouselScrollbar.swift
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
//
|
||||||
|
// CarouselScrollbar.swift
|
||||||
|
// VDS
|
||||||
|
//
|
||||||
|
// Created by Kanamarlapudi, Vasavi on 12/03/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import VDSColorTokens
|
||||||
|
import VDSFormControlsTokens
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
/// A carousel scrollbar is a control that allows to navigate between items in a carousel.
|
||||||
|
/// It's also a status indicator that conveys the relative amount of content in a carousel and a location within it.
|
||||||
|
@objc(VDSCarouselScrollbar)
|
||||||
|
open class CarouselScrollbar: View {
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Initializers
|
||||||
|
//--------------------------------------------------
|
||||||
|
required public init() {
|
||||||
|
super.init(frame: .zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override init(frame: CGRect) {
|
||||||
|
super.init(frame: .zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
public required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Public Properties
|
||||||
|
//--------------------------------------------------
|
||||||
|
/// Used to set total number of slides within carousel
|
||||||
|
open var numberOfSlides: Int {
|
||||||
|
get { return _numberOfSlides }
|
||||||
|
set {
|
||||||
|
_numberOfSlides = newValue
|
||||||
|
setThumbWidth()
|
||||||
|
scrollThumbToPosition(position)
|
||||||
|
setNeedsUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of slides that can appear at once in a set in a carousel container.
|
||||||
|
open var selectedLayout: Layout? {
|
||||||
|
get { return _selectedLayout }
|
||||||
|
set {
|
||||||
|
if let newValue {
|
||||||
|
_selectedLayout = newValue
|
||||||
|
} else {
|
||||||
|
_selectedLayout = .oneUP
|
||||||
|
}
|
||||||
|
setThumbWidth()
|
||||||
|
scrollThumbToPosition(position)
|
||||||
|
setNeedsUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enum used to describe the number of slides that can appear at once in a set in a carousel container.
|
||||||
|
public enum Layout: String, CaseIterable {
|
||||||
|
case oneUP = "1UP"
|
||||||
|
case twoUP = "2UP"
|
||||||
|
case threeUP = "3UP"
|
||||||
|
case fourUP = "4UP"
|
||||||
|
case fiveUP = "5UP"
|
||||||
|
case sixUP = "6UP"
|
||||||
|
case eightUP = "8UP"
|
||||||
|
|
||||||
|
var value: Int {
|
||||||
|
switch self {
|
||||||
|
case .oneUP:
|
||||||
|
1
|
||||||
|
case .twoUP:
|
||||||
|
2
|
||||||
|
case .threeUP:
|
||||||
|
3
|
||||||
|
case .fourUP:
|
||||||
|
4
|
||||||
|
case .fiveUP:
|
||||||
|
5
|
||||||
|
case .sixUP:
|
||||||
|
6
|
||||||
|
case .eightUP:
|
||||||
|
8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to set the position of the thumb(scrubber). This is used when the carousel container changes position, it will align the position of thumb(scrubber).
|
||||||
|
open var position: Int {
|
||||||
|
get { return _position }
|
||||||
|
set {
|
||||||
|
checkPositions()
|
||||||
|
_position = (newValue > totalPositions) ? totalPositions : newValue
|
||||||
|
scrollThumbToPosition(position)
|
||||||
|
setNeedsUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows a unique id to be passed into the thumb and track of the thumb(scrubber).
|
||||||
|
open var scrubberId: Int? { didSet { setNeedsUpdate() } }
|
||||||
|
|
||||||
|
/// A callback when the scrubber position changes. Passes parameters (position).
|
||||||
|
open var onScrubberDrag: ((Int) -> Void)? {
|
||||||
|
get { nil }
|
||||||
|
set {
|
||||||
|
onScrubberDragCancellable?.cancel()
|
||||||
|
if let newValue {
|
||||||
|
onScrubberDragCancellable = onScrubberDragPublisher
|
||||||
|
.sink { c in
|
||||||
|
newValue(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A publisher for when the scrubber position changes. Passes parameters (position).
|
||||||
|
open var onScrubberDragPublisher = PassthroughSubject<Int, Never>()
|
||||||
|
private var onScrubberDragCancellable: AnyCancellable?
|
||||||
|
|
||||||
|
/// A callback when the thumb move forward. Passes parameters (position).
|
||||||
|
open var onMoveForward: ((Int) -> Void)? {
|
||||||
|
get { nil }
|
||||||
|
set {
|
||||||
|
onMoveForwardCancellable?.cancel()
|
||||||
|
if let newValue {
|
||||||
|
onMoveForwardCancellable = onMoveForwardPublisher
|
||||||
|
.sink { c in
|
||||||
|
newValue(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A publisher for when the thumb move forward. Passes parameters (position).
|
||||||
|
open var onMoveForwardPublisher = PassthroughSubject<Int, Never>()
|
||||||
|
private var onMoveForwardCancellable: AnyCancellable?
|
||||||
|
|
||||||
|
/// A callback when the thumb move backward. Passes parameters (position).
|
||||||
|
open var onMoveBackward: ((Int) -> Void)? {
|
||||||
|
get { nil }
|
||||||
|
set {
|
||||||
|
onMoveBackwardCancellable?.cancel()
|
||||||
|
if let newValue {
|
||||||
|
onMoveBackwardCancellable = onMoveBackwardPublisher
|
||||||
|
.sink { c in
|
||||||
|
newValue(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A publisher for when the thumb move backward. Passes parameters (position).
|
||||||
|
open var onMoveBackwardPublisher = PassthroughSubject<Int, Never>()
|
||||||
|
private var onMoveBackwardCancellable: AnyCancellable?
|
||||||
|
|
||||||
|
/// A callback when the thumb touch start. Passes parameters (position).
|
||||||
|
open var onThumbTouchStart: ((Int) -> Void)? {
|
||||||
|
get { nil }
|
||||||
|
set {
|
||||||
|
onThumbTouchStartCancellable?.cancel()
|
||||||
|
if let newValue {
|
||||||
|
onThumbTouchStartCancellable = onThumbTouchStartPublisher
|
||||||
|
.sink { c in
|
||||||
|
newValue(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A publisher for when the thumb touch start. Passes parameters (position).
|
||||||
|
open var onThumbTouchStartPublisher = PassthroughSubject<Int, Never>()
|
||||||
|
private var onThumbTouchStartCancellable: AnyCancellable?
|
||||||
|
|
||||||
|
/// A callback when the thumb touch end. Passes parameters (position).
|
||||||
|
open var onThumbTouchEnd: ((Int) -> Void)? {
|
||||||
|
get { nil }
|
||||||
|
set {
|
||||||
|
onThumbTouchEndCancellable?.cancel()
|
||||||
|
if let newValue {
|
||||||
|
onThumbTouchEndCancellable = onThumbTouchEndPublisher
|
||||||
|
.sink { c in
|
||||||
|
newValue(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A publisher for when the thumb touch end. Passes parameters (position).
|
||||||
|
open var onThumbTouchEndPublisher = PassthroughSubject<Int, Never>()
|
||||||
|
private var onThumbTouchEndCancellable: AnyCancellable?
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Private Properties
|
||||||
|
//--------------------------------------------------
|
||||||
|
// Sizes are from InVision design specs.
|
||||||
|
internal var containerSize: CGSize { CGSize(width: 45, height: 44) }
|
||||||
|
internal var _selectedLayout: Layout = .oneUP
|
||||||
|
internal var _numberOfSlides: Int = 1
|
||||||
|
internal var totalPositions: Int = 1
|
||||||
|
internal var _position: Int = 1
|
||||||
|
internal var trayOriginalCenter: CGPoint!
|
||||||
|
|
||||||
|
private let trackViewWidth = 96
|
||||||
|
private let trackViewHeight: CGFloat = 4
|
||||||
|
private let minThumbWidth: Float = 16.0
|
||||||
|
private var thumbWidth: Float = 16.0
|
||||||
|
private var computedWidth: Float = 0.0
|
||||||
|
private let cornerRadius: CGFloat = 2.0
|
||||||
|
private let activeOpacity: Float = 0.15
|
||||||
|
private let defaultOpacity: Float = 1
|
||||||
|
|
||||||
|
internal var containerView = View().with {
|
||||||
|
$0.clipsToBounds = true
|
||||||
|
}
|
||||||
|
internal var trackView = View()
|
||||||
|
internal var leftActiveOverlay = View()
|
||||||
|
internal var rightActiveOverlay = View()
|
||||||
|
internal var thumbView = View()
|
||||||
|
|
||||||
|
internal var rightActiveOverlayLayer: CALayer = CALayer()
|
||||||
|
internal var leftActiveOverlayLayer: CALayer = CALayer()
|
||||||
|
internal var thumbViewLayer: CALayer = CALayer()
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Configuration
|
||||||
|
//--------------------------------------------------
|
||||||
|
private var thumbColorConfiguration = SurfaceColorConfiguration(VDSColor.interactiveScrollthumbOnlight , VDSColor.interactiveScrollthumbOndark)
|
||||||
|
private var trackColorConfiguration = SurfaceColorConfiguration(VDSColor.interactiveScrolltrackOnlight , VDSColor.interactiveScrolltrackOndark)
|
||||||
|
private var activeOverlayColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteWhite)
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Lifecycle
|
||||||
|
//--------------------------------------------------
|
||||||
|
open override func initialSetup() {
|
||||||
|
super.initialSetup()
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func setup() {
|
||||||
|
super.setup()
|
||||||
|
isAccessibilityElement = true
|
||||||
|
accessibilityLabel = "Carousel Scrollbar"
|
||||||
|
|
||||||
|
addSubview(containerView)
|
||||||
|
containerView
|
||||||
|
.pinTop()
|
||||||
|
.pinBottom()
|
||||||
|
.pinLeadingGreaterThanOrEqualTo()
|
||||||
|
.pinTrailingLessThanOrEqualTo()
|
||||||
|
.height(containerSize.height)
|
||||||
|
.width(CGFloat(trackViewWidth))
|
||||||
|
|
||||||
|
containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate()
|
||||||
|
|
||||||
|
//Trackview
|
||||||
|
trackView.frame = CGRectMake(0, 20, CGFloat(trackViewWidth), trackViewHeight)
|
||||||
|
trackView.layer.cornerRadius = cornerRadius
|
||||||
|
containerView.addSubview(trackView)
|
||||||
|
|
||||||
|
///Left active overlay
|
||||||
|
leftActiveOverlay.frame = CGRectMake(trackView.frame.origin.x, 0, CGFloat(trackViewWidth), containerSize.height)
|
||||||
|
leftActiveOverlay.isUserInteractionEnabled = true
|
||||||
|
leftActiveOverlay.backgroundColor = .clear
|
||||||
|
let leftPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(onLeftViewLongPressRecognizer(_:)))
|
||||||
|
leftPressRecognizer.minimumPressDuration = 0
|
||||||
|
leftActiveOverlay.addGestureRecognizer(leftPressRecognizer)
|
||||||
|
containerView.addSubview(leftActiveOverlay)
|
||||||
|
|
||||||
|
leftActiveOverlay.layer.addSublayer(leftActiveOverlayLayer)
|
||||||
|
leftActiveOverlayLayer.cornerRadius = cornerRadius
|
||||||
|
leftActiveOverlayLayer.frame = .init(origin: .zero, size: .init(width: leftActiveOverlayLayer.frame.size.width, height: trackViewHeight))
|
||||||
|
|
||||||
|
///Right active overlay
|
||||||
|
rightActiveOverlay.frame = CGRectMake(thumbView.frame.origin.x + thumbView.frame.size.width, 0, CGFloat(trackViewWidth), containerSize.height)
|
||||||
|
rightActiveOverlay.isUserInteractionEnabled = true
|
||||||
|
rightActiveOverlay.backgroundColor = .clear
|
||||||
|
let rightPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(onRightViewLongPressRecognizer(_:)))
|
||||||
|
rightPressRecognizer.minimumPressDuration = 0
|
||||||
|
rightActiveOverlay.addGestureRecognizer(rightPressRecognizer)
|
||||||
|
containerView.addSubview(rightActiveOverlay)
|
||||||
|
|
||||||
|
rightActiveOverlay.layer.addSublayer(rightActiveOverlayLayer)
|
||||||
|
rightActiveOverlayLayer.cornerRadius = cornerRadius
|
||||||
|
rightActiveOverlayLayer.frame = .init(origin: .zero, size: .init(width: rightActiveOverlay.frame.size.width, height: trackViewHeight))
|
||||||
|
|
||||||
|
//Thumbview
|
||||||
|
thumbView.frame = CGRectMake(0, 0, CGFloat(thumbWidth), containerSize.height)
|
||||||
|
thumbView.backgroundColor = .clear
|
||||||
|
thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(onScrubberChange(_:)))))
|
||||||
|
containerView.addSubview(thumbView)
|
||||||
|
updateActiveOverlays()
|
||||||
|
|
||||||
|
thumbViewLayer.cornerRadius = cornerRadius
|
||||||
|
thumbViewLayer.backgroundColor = thumbColorConfiguration.getColor(surface).cgColor
|
||||||
|
thumbViewLayer.frame = .init(origin: .init(x: 0, y: 20), size: .init(width: CGFloat(thumbWidth), height: trackViewHeight))
|
||||||
|
thumbView.layer.addSublayer(thumbViewLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func updateView() {
|
||||||
|
super.updateView()
|
||||||
|
trackView.backgroundColor = trackColorConfiguration.getColor(surface)
|
||||||
|
thumbViewLayer.backgroundColor = thumbColorConfiguration.getColor(surface).cgColor
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func updateAccessibility() {
|
||||||
|
super.updateAccessibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func reset() {
|
||||||
|
for subview in subviews {
|
||||||
|
for recognizer in subview.gestureRecognizers ?? [] {
|
||||||
|
subview.removeGestureRecognizer(recognizer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Private Methods
|
||||||
|
//--------------------------------------------------
|
||||||
|
func movePositionBackward() {
|
||||||
|
position = position - 1
|
||||||
|
scrollThumbToPosition(position)
|
||||||
|
onMoveBackwardPublisher.send(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
func movePositionForward() {
|
||||||
|
position = position + 1
|
||||||
|
scrollThumbToPosition(position)
|
||||||
|
onMoveForwardPublisher.send(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute track width and should maintain minimum thumb width if needed
|
||||||
|
private func setThumbWidth() {
|
||||||
|
let width = (Float(trackViewWidth) / Float(numberOfSlides)) * Float(_selectedLayout.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)
|
||||||
|
thumbView.frame.origin.x = trackView.frame.origin.x
|
||||||
|
thumbViewLayer.frame.size.width = thumbView.frame.size.width
|
||||||
|
checkPositions()
|
||||||
|
updateActiveOverlays()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incomplete set moves a shorter distance than the standard increment value.
|
||||||
|
// Update active overlay frames according to thumb position.
|
||||||
|
private func updateActiveOverlays() {
|
||||||
|
// adjusting thumb position if it goes beyond trackView on left/right.
|
||||||
|
let thumbPosition = thumbView.frame.origin.x + thumbView.frame.size.width
|
||||||
|
let trackPosition = trackView.frame.origin.x + trackView.frame.size.width
|
||||||
|
if thumbPosition > trackPosition {
|
||||||
|
thumbView.frame.origin.x = trackPosition - thumbView.frame.size.width
|
||||||
|
} else if thumbView.frame.origin.x < trackView.frame.origin.x {
|
||||||
|
thumbView.frame.origin.x = trackView.frame.origin.x
|
||||||
|
}
|
||||||
|
|
||||||
|
//left active overlay position update
|
||||||
|
leftActiveOverlay.frame.size.width = thumbView.frame.origin.x - trackView.frame.origin.x + cornerRadius
|
||||||
|
leftActiveOverlayLayer.frame = .init(origin: .init(x: 0, y: 20), size: .init(width: leftActiveOverlay.frame.size.width, height: trackViewHeight))
|
||||||
|
|
||||||
|
//right active overlay position update
|
||||||
|
rightActiveOverlay.frame.origin.x = thumbView.frame.origin.x + thumbView.frame.size.width - cornerRadius
|
||||||
|
rightActiveOverlay.frame.size.width = (trackView.frame.origin.x + trackView.frame.size.width) - (thumbView.frame.origin.x + thumbView.frame.size.width) + cornerRadius
|
||||||
|
rightActiveOverlayLayer.frame = .init(origin: .init(x: 0, y: 20), size: .init(width: rightActiveOverlay.frame.size.width, height: trackViewHeight))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func checkPositions() {
|
||||||
|
totalPositions = Int (ceil (Double(numberOfSlides) / Double(_selectedLayout.value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func scrollThumbToPosition(_ position: Int) {
|
||||||
|
setThumb(at: position)
|
||||||
|
onScrubberDragPublisher.send(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setThumb(at position: Int) {
|
||||||
|
UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
self.thumbView.frame.origin.x = CGFloat(Float((position) - 1) * self.computedWidth) + self.trackView.frame.origin.x
|
||||||
|
self.updateActiveOverlays()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Gesture Methods
|
||||||
|
//--------------------------------------------------
|
||||||
|
// Drag scrollbar thumb to move it to the left or right.
|
||||||
|
// Upon releases of drag, the scrollbar thumb snaps to the closest full position and the scrollbar returns to original size without delay.
|
||||||
|
@objc func onScrubberChange(_ sender: UIPanGestureRecognizer) {
|
||||||
|
let translation = sender.translation(in: thumbView)
|
||||||
|
if sender.state == UIGestureRecognizer.State.began {
|
||||||
|
trayOriginalCenter = thumbView.center
|
||||||
|
onThumbTouchStartPublisher.send(position)
|
||||||
|
} else if sender.state == UIGestureRecognizer.State.changed {
|
||||||
|
let draggedPositions = Int (ceil (Double(translation.x) / Double(computedWidth)))
|
||||||
|
setThumb(at: position + draggedPositions)
|
||||||
|
}
|
||||||
|
else if sender.state == UIGestureRecognizer.State.cancelled || sender.state == UIGestureRecognizer.State.ended {
|
||||||
|
let draggedPositions = Int (ceil (Double(translation.x) / Double(computedWidth)))
|
||||||
|
position = ((position + draggedPositions) < 1) ? 1 : (position + draggedPositions)
|
||||||
|
if sender.state == UIGestureRecognizer.State.ended {
|
||||||
|
onThumbTouchEndPublisher.send(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the scrollbar thumb to the left while tapping on the left side of the scrubber.
|
||||||
|
@objc func onLeftViewLongPressRecognizer(_ gesture: UILongPressGestureRecognizer) {
|
||||||
|
animateOverlay(layer: leftActiveOverlayLayer, with: gesture, onGestureEnd: movePositionBackward)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the scrollbar thumb to the right while tapping on the right side of the scrubber.
|
||||||
|
@objc func onRightViewLongPressRecognizer(_ gesture: UILongPressGestureRecognizer) {
|
||||||
|
animateOverlay(layer: rightActiveOverlayLayer, with: gesture, onGestureEnd: movePositionForward)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func animateOverlay(layer: CALayer, with gesture: UILongPressGestureRecognizer, onGestureEnd: @escaping(() -> Void)) {
|
||||||
|
UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
if gesture.state == .began {
|
||||||
|
layer.backgroundColor = activeOverlayColorConfiguration.getColor(self).cgColor
|
||||||
|
layer.opacity = activeOpacity
|
||||||
|
} else if gesture.state == .cancelled {
|
||||||
|
layer.backgroundColor = UIColor.clear.cgColor
|
||||||
|
layer.opacity = defaultOpacity
|
||||||
|
} else if gesture.state == .ended {
|
||||||
|
layer.backgroundColor = UIColor.clear.cgColor
|
||||||
|
layer.opacity = defaultOpacity
|
||||||
|
onGestureEnd()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
MM/DD/YYYY
|
||||||
|
----------------
|
||||||
|
|
||||||
|
07/29/22
|
||||||
|
----------------
|
||||||
|
- Initial Brand 3.0 handoff
|
||||||
|
|
||||||
|
08/10/2022
|
||||||
|
----------------
|
||||||
|
- Updated default and inverted prop to light and dark surface.
|
||||||
|
|
||||||
|
11/30/2022
|
||||||
|
----------------
|
||||||
|
- Added "(web only)" to any instance of "keyboard focus"
|
||||||
|
|
||||||
|
12/13/2022
|
||||||
|
----------------
|
||||||
|
- Replaced focus border pixel and style & spacing values with tokens.
|
||||||
|
|
||||||
|
01/09/2023
|
||||||
|
----------------
|
||||||
|
- Updated Specs to use new SPEC Templates and SPEC DOC Components.
|
||||||
|
|
||||||
|
05/19/2023
|
||||||
|
----------------
|
||||||
|
- Changed Carousel Scrubber to Carousel Scrollbar and replaced all instances of Scrubber to Scrollbar.
|
||||||
|
- Removed KF states and behaviors from States and Behaviors > Interaction Types.
|
||||||
|
|
||||||
@ -25,6 +25,7 @@ Using the system allows designers and developers to collaborate more easily and
|
|||||||
- ``Button``
|
- ``Button``
|
||||||
- ``ButtonIcon``
|
- ``ButtonIcon``
|
||||||
- ``ButtonGroup``
|
- ``ButtonGroup``
|
||||||
|
- ``CarouselScrollbar``
|
||||||
- ``Checkbox``
|
- ``Checkbox``
|
||||||
- ``CheckboxItem``
|
- ``CheckboxItem``
|
||||||
- ``CheckboxGroup``
|
- ``CheckboxGroup``
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user