Digital ACT-191 ONEAPP-6830 story: increased hit area to thumb and track
This commit is contained in:
parent
1387cb6bf4
commit
5356650355
@ -122,7 +122,6 @@ open class CarouselScrollbar: View {
|
||||
internal var containerSize: CGSize { CGSize(width: 45, height: 44) }
|
||||
internal var _selectedLayout: Layout = .oneUP
|
||||
internal var _numberOfSlides: Int? = 1
|
||||
internal var heightConstraint: NSLayoutConstraint?
|
||||
internal var totalPositions: Int? = 1
|
||||
internal var _position: Int? = 1
|
||||
internal var trayOriginalCenter: CGPoint!
|
||||
@ -131,41 +130,30 @@ open class CarouselScrollbar: View {
|
||||
private let trackViewHeight: CGFloat = 4
|
||||
private let minThumbWidth: Float = 16.0
|
||||
private var thumbWidth: Float = 16.0
|
||||
private var actualThumbWidth: Float = 0.0
|
||||
private var computedWidth: Float = 0.0
|
||||
private let cornerRadius: CGFloat = 4.0
|
||||
private let activeOpacity: Float = 0.15
|
||||
private let defaultOpacity: Float = 1
|
||||
|
||||
/// Track view with fixed width
|
||||
internal var trackView: UIView = {
|
||||
return UIView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
}()
|
||||
|
||||
/// Left Active Track overlay with variable width
|
||||
internal var leftActiveOverlay: UIView = {
|
||||
return UIView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.tag = 2
|
||||
}
|
||||
}()
|
||||
|
||||
/// Right Active Track overlay with variable width
|
||||
internal var rightActiveOverlay: UIView = {
|
||||
return UIView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.tag = 3
|
||||
}
|
||||
}()
|
||||
|
||||
/// Thumb view with variable width
|
||||
internal var thumbView: UIView = {
|
||||
return UIView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
}()
|
||||
internal var containerView = View().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
internal var trackView = View().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
internal var leftActiveOverlay = View().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
internal var rightActiveOverlay = View().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
internal var thumbView = View().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
|
||||
internal var rightActiveOverlayLayer: CALayer = CALayer()
|
||||
internal var leftActiveOverlayLayer: CALayer = CALayer()
|
||||
internal var thumbViewLayer: CALayer = CALayer()
|
||||
//--------------------------------------------------
|
||||
// MARK: - Configuration
|
||||
//--------------------------------------------------
|
||||
@ -184,46 +172,67 @@ open class CarouselScrollbar: View {
|
||||
super.setup()
|
||||
accessibilityLabel = "Carousel Scrollbar"
|
||||
|
||||
//create the wrapping view
|
||||
heightConstraint = self.heightAnchor.constraint(equalToConstant: containerSize.height)
|
||||
heightConstraint?.priority = .defaultHigh
|
||||
heightConstraint?.isActive = true
|
||||
addSubview(containerView)
|
||||
containerView
|
||||
.pinTop()
|
||||
.pinBottom()
|
||||
.pinLeadingGreaterThanOrEqualTo()
|
||||
.pinTrailingLessThanOrEqualTo()
|
||||
.height(containerSize.height)
|
||||
.width(CGFloat(trackViewWidth))
|
||||
|
||||
containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate()
|
||||
|
||||
//Trackview
|
||||
trackView.frame = CGRectMake(20, 20, CGFloat(trackViewWidth), trackViewHeight)
|
||||
trackView.frame = CGRectMake(0, 20, CGFloat(trackViewWidth), trackViewHeight)
|
||||
trackView.layer.cornerRadius = cornerRadius
|
||||
addSubview(trackView)
|
||||
containerView.addSubview(trackView)
|
||||
|
||||
///Left active overlay
|
||||
leftActiveOverlay.frame = CGRectMake(trackView.frame.origin.x, 20, CGFloat(trackViewWidth), trackViewHeight)
|
||||
leftActiveOverlay.frame = CGRectMake(trackView.frame.origin.x, 0, CGFloat(trackViewWidth), containerSize.height)
|
||||
leftActiveOverlay.isUserInteractionEnabled = true
|
||||
leftActiveOverlay.layer.cornerRadius = cornerRadius
|
||||
leftActiveOverlay.backgroundColor = .clear
|
||||
// leftActiveOverlay.clipsToBounds = true
|
||||
let leftPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.onThumbTouchStart(_:)))
|
||||
leftPressRecognizer.minimumPressDuration = 0
|
||||
leftActiveOverlay.addGestureRecognizer(leftPressRecognizer)
|
||||
addSubview(leftActiveOverlay)
|
||||
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, 20, CGFloat(trackViewWidth), trackViewHeight)
|
||||
rightActiveOverlay.frame = CGRectMake(thumbView.frame.origin.x + thumbView.frame.size.width, 0, CGFloat(trackViewWidth), containerSize.height)
|
||||
rightActiveOverlay.isUserInteractionEnabled = true
|
||||
rightActiveOverlay.layer.cornerRadius = cornerRadius
|
||||
// rightActiveOverlay.clipsToBounds = true
|
||||
rightActiveOverlay.backgroundColor = .clear
|
||||
let rightPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.onThumbTouchEnd(_:)))
|
||||
rightPressRecognizer.minimumPressDuration = 0
|
||||
rightActiveOverlay.addGestureRecognizer(rightPressRecognizer)
|
||||
addSubview(rightActiveOverlay)
|
||||
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(20, 20, CGFloat(thumbWidth), trackViewHeight)
|
||||
thumbView.layer.cornerRadius = cornerRadius
|
||||
thumbView.frame = CGRectMake(0, 0, CGFloat(thumbWidth), containerSize.height)
|
||||
thumbView.backgroundColor = .clear
|
||||
thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:(#selector(self.onScrubberDrag(_:)))))
|
||||
addSubview(thumbView)
|
||||
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)
|
||||
thumbView.backgroundColor = thumbColorConfiguration.getColor(surface)
|
||||
thumbViewLayer.backgroundColor = thumbColorConfiguration.getColor(surface).cgColor
|
||||
}
|
||||
|
||||
open override func updateAccessibility() {
|
||||
@ -252,67 +261,22 @@ open class CarouselScrollbar: View {
|
||||
scrollThumbToPosition(position)
|
||||
}
|
||||
|
||||
// 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 onScrubberDrag(_ sender: UIPanGestureRecognizer) {
|
||||
let translation = sender.translation(in: thumbView)
|
||||
if sender.state == UIGestureRecognizer.State.began {
|
||||
trayOriginalCenter = thumbView.center
|
||||
} else if sender.state == UIGestureRecognizer.State.changed {
|
||||
let draggedPositions = Int (ceil (Double(translation.x) / Double(actualThumbWidth)))
|
||||
setThumb(at: (position ?? 1) + draggedPositions)
|
||||
}
|
||||
else if sender.state == UIGestureRecognizer.State.cancelled || sender.state == UIGestureRecognizer.State.ended {
|
||||
let draggedPositions = Int (ceil (Double(translation.x) / Double(actualThumbWidth)))
|
||||
position = (((position ?? 1) + draggedPositions) < 1) ? 1 : ((position ?? 1) + draggedPositions)
|
||||
}
|
||||
}
|
||||
|
||||
// Move the scrollbar thumb to the left while tapping on the left side of the scrubber.
|
||||
@objc func onThumbTouchStart(_ gesture: UILongPressGestureRecognizer) {
|
||||
if gesture.state == .began {
|
||||
leftActiveOverlay.backgroundColor = activeOverlayColorConfiguration.getColor(self)
|
||||
leftActiveOverlay.layer.opacity = activeOpacity
|
||||
} else if gesture.state == .cancelled {
|
||||
leftActiveOverlay.backgroundColor = .clear
|
||||
leftActiveOverlay.layer.opacity = defaultOpacity
|
||||
} else if gesture.state == .ended {
|
||||
leftActiveOverlay.backgroundColor = .clear
|
||||
leftActiveOverlay.layer.opacity = defaultOpacity
|
||||
self.onMoveBackward()
|
||||
}
|
||||
}
|
||||
|
||||
// Move the scrollbar thumb to the right while tapping on the right side of the scrubber.
|
||||
@objc func onThumbTouchEnd(_ gesture: UILongPressGestureRecognizer) {
|
||||
if gesture.state == .began {
|
||||
rightActiveOverlay.backgroundColor = activeOverlayColorConfiguration.getColor(self)
|
||||
rightActiveOverlay.layer.opacity = activeOpacity
|
||||
} else if gesture.state == .cancelled {
|
||||
rightActiveOverlay.backgroundColor = .clear
|
||||
rightActiveOverlay.layer.opacity = defaultOpacity
|
||||
} else if gesture.state == .ended {
|
||||
rightActiveOverlay.backgroundColor = .clear
|
||||
rightActiveOverlay.layer.opacity = defaultOpacity
|
||||
self.onMoveForward()
|
||||
}
|
||||
}
|
||||
|
||||
// Minimum thumb width applied
|
||||
// Incomplete set moves a shorter distance than the standard increment value.
|
||||
// Compute track width and should maintain minimum thumb width if needed
|
||||
private func setThumbWidth() {
|
||||
let width = (Float(trackViewWidth) / Float(numberOfSlides ?? 1)) * Float(_selectedLayout.value)
|
||||
actualThumbWidth = (width > Float(trackViewWidth)) ? Float(trackViewWidth) : width
|
||||
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.
|
||||
// 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 {
|
||||
@ -323,26 +287,80 @@ open class CarouselScrollbar: View {
|
||||
|
||||
//left active overlay position update
|
||||
leftActiveOverlay.frame.size.width = thumbView.frame.origin.x - trackView.frame.origin.x + cornerRadius
|
||||
|
||||
//left active overlay position update
|
||||
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))
|
||||
}
|
||||
|
||||
// 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 onScrubberDrag(_ sender: UIPanGestureRecognizer) {
|
||||
let translation = sender.translation(in: thumbView)
|
||||
if sender.state == UIGestureRecognizer.State.began {
|
||||
trayOriginalCenter = thumbView.center
|
||||
} else if sender.state == UIGestureRecognizer.State.changed {
|
||||
let draggedPositions = Int (ceil (Double(translation.x) / Double(computedWidth)))
|
||||
setThumb(at: (position ?? 1) + draggedPositions)
|
||||
}
|
||||
else if sender.state == UIGestureRecognizer.State.cancelled || sender.state == UIGestureRecognizer.State.ended {
|
||||
let draggedPositions = Int (ceil (Double(translation.x) / Double(computedWidth)))
|
||||
position = (((position ?? 1) + draggedPositions) < 1) ? 1 : ((position ?? 1) + draggedPositions)
|
||||
}
|
||||
}
|
||||
|
||||
// Move the scrollbar thumb to the left while tapping on the left side of the scrubber.
|
||||
@objc func onThumbTouchStart(_ gesture: UILongPressGestureRecognizer) {
|
||||
UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { [self] in
|
||||
if gesture.state == .began {
|
||||
leftActiveOverlayLayer.backgroundColor = activeOverlayColorConfiguration.getColor(self).cgColor
|
||||
leftActiveOverlayLayer.opacity = activeOpacity
|
||||
} else if gesture.state == .cancelled {
|
||||
leftActiveOverlayLayer.backgroundColor = UIColor.clear.cgColor
|
||||
leftActiveOverlayLayer.opacity = defaultOpacity
|
||||
} else if gesture.state == .ended {
|
||||
leftActiveOverlayLayer.backgroundColor = UIColor.clear.cgColor
|
||||
leftActiveOverlayLayer.opacity = defaultOpacity
|
||||
UIView.performWithoutAnimation {
|
||||
self.onMoveBackward()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Move the scrollbar thumb to the right while tapping on the right side of the scrubber.
|
||||
@objc func onThumbTouchEnd(_ gesture: UILongPressGestureRecognizer) {
|
||||
UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { [self] in
|
||||
if gesture.state == .began {
|
||||
rightActiveOverlayLayer.backgroundColor = activeOverlayColorConfiguration.getColor(self).cgColor
|
||||
rightActiveOverlayLayer.opacity = activeOpacity
|
||||
} else if gesture.state == .cancelled {
|
||||
rightActiveOverlayLayer.backgroundColor = UIColor.clear.cgColor
|
||||
rightActiveOverlayLayer.opacity = defaultOpacity
|
||||
} else if gesture.state == .ended {
|
||||
rightActiveOverlayLayer.backgroundColor = UIColor.clear.cgColor
|
||||
rightActiveOverlayLayer.opacity = defaultOpacity
|
||||
self.onMoveForward()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func checkPositions() {
|
||||
totalPositions = Int (ceil (Double(numberOfSlides ?? 1) / Double(_selectedLayout.value)))
|
||||
}
|
||||
|
||||
|
||||
private func scrollThumbToPosition(_ position: Int?) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in
|
||||
self?.setThumb(at: position)
|
||||
self?.onScrubberDidChange?(position ?? 1)
|
||||
}
|
||||
setThumb(at: position)
|
||||
onScrubberDidChange?(position ?? 1)
|
||||
}
|
||||
|
||||
private func setThumb(at position: Int?) {
|
||||
thumbView.frame.origin.x = CGFloat(Float((position ?? 1) - 1) * actualThumbWidth) + trackView.frame.origin.x
|
||||
updateActiveOverlays()
|
||||
UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: {
|
||||
self.thumbView.frame.origin.x = CGFloat(Float((position ?? 1) - 1) * self.computedWidth) + self.trackView.frame.origin.x
|
||||
self.updateActiveOverlays()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user