added range support for slider

This commit is contained in:
yaanvar
2023-09-25 12:21:31 +03:00
parent dc39072de3
commit 276863efeb
22 changed files with 312 additions and 171 deletions
+14
View File
@@ -11277,6 +11277,20 @@
"client/ios/Tests/reference_snapshots/div-slider/fixed_size_414@3x.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/fixed_size_414@3x.png",
"client/ios/Tests/reference_snapshots/div-slider/match_parent_size_375@2x.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/match_parent_size_375@2x.png",
"client/ios/Tests/reference_snapshots/div-slider/match_parent_size_414@3x.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/match_parent_size_414@3x.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x_step0.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x_step0.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x_step1.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x_step1.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x_step2.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x_step2.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x_step3.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x_step3.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x_step4.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x_step4.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x_step5.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_375@2x_step5.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x_step0.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x_step0.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x_step1.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x_step1.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x_step2.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x_step2.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x_step3.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x_step3.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x_step4.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x_step4.png",
"client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x_step5.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/ranges_414@3x_step5.png",
"client/ios/Tests/reference_snapshots/div-slider/rtl_slider_375@2x.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/rtl_slider_375@2x.png",
"client/ios/Tests/reference_snapshots/div-slider/rtl_slider_414@3x.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/rtl_slider_414@3x.png",
"client/ios/Tests/reference_snapshots/div-slider/secondary_thumb_on_left_375@2x.png":"divkit/public/client/ios/Tests/reference_snapshots/div-slider/secondary_thumb_on_left_375@2x.png",
@@ -19,16 +19,42 @@ extension DivSlider: DivBlockModeling {
private func makeBaseBlock(context: DivBlockModelingContext) throws -> Block {
let expressionResolver = context.expressionResolver
let firstThumbValue: Binding<Int> = thumbValueVariable.flatMap {
context.makeBinding(variableName: $0, defaultValue: 0)
} ?? .zero
let firstThumb = SliderModel.ThumbModel(
block: makeThumbBlock(
thumb: thumbStyle
.makeBlock(context: context, corners: .all),
textBlock: thumbTextStyle?.makeThumbTextBlock(
context: context,
value: firstThumbValue.value
),
textOffset: thumbTextStyle.flatMap {
CGPoint(
x: $0.offset?.x.resolveValue(expressionResolver) ?? 0,
y: $0.offset?.y.resolveValue(expressionResolver) ?? 0
)
} ?? .zero
),
value: firstThumbValue,
size: CGSize(
width: thumbStyle.getWidth(context: context),
height: thumbStyle.getHeight(context: context)
),
offsetX: thumbTextStyle?.offset?.x.resolveValue(expressionResolver) ?? 0,
offsetY: thumbTextStyle?.offset?.y.resolveValue(expressionResolver) ?? 0
)
let secondThumb: SliderModel.ThumbModel?
if let thumbSecondaryStyle = thumbSecondaryStyle,
let thumbSecondaryValueVariable = thumbSecondaryValueVariable {
if let thumbSecondaryValueVariable = thumbSecondaryValueVariable {
let secondThumbValue = context.makeBinding(
variableName: thumbSecondaryValueVariable,
defaultValue: 0
)
secondThumb = SliderModel.ThumbModel(
block: makeThumbBlock(
thumb: thumbSecondaryStyle
thumb: thumbSecondaryStyle?.makeBlock(context: context, corners: .all) ?? thumbStyle
.makeBlock(context: context, corners: .all),
textBlock: (thumbSecondaryTextStyle ?? thumbTextStyle)?.makeThumbTextBlock(
context: context,
@@ -43,8 +69,10 @@ extension DivSlider: DivBlockModeling {
),
value: secondThumbValue,
size: CGSize(
width: thumbSecondaryStyle.getWidth(context: context),
height: thumbSecondaryStyle.getHeight(context: context)
width: thumbSecondaryStyle?.getWidth(context: context) ?? thumbStyle
.getWidth(context: context),
height: thumbSecondaryStyle?.getHeight(context: context) ?? thumbStyle
.getHeight(context: context)
),
offsetX: thumbSecondaryTextStyle?.offset?.x.resolveValue(expressionResolver) ?? 0,
offsetY: thumbSecondaryTextStyle?.offset?.y.resolveValue(expressionResolver) ?? 0
@@ -53,90 +81,117 @@ extension DivSlider: DivBlockModeling {
secondThumb = nil
}
let firstThumbValue: Binding<Int> = thumbValueVariable.flatMap {
context.makeBinding(variableName: $0, defaultValue: 0)
} ?? .zero
let activeMark = makeRoundedRectangle(with: tickMarkActiveStyle, resolver: expressionResolver)
let inactiveMark = makeRoundedRectangle(
with: tickMarkInactiveStyle,
resolver: expressionResolver
)
let minValue = resolveMinValue(expressionResolver)
let maxValue = resolveMaxValue(expressionResolver)
let marksConfiguration = MarksConfiguration(
minValue: CGFloat(resolveMinValue(expressionResolver)),
maxValue: CGFloat(resolveMaxValue(expressionResolver)),
minValue: CGFloat(minValue),
maxValue: CGFloat(maxValue),
activeMark: activeMark ?? .empty,
inactiveMark: inactiveMark ?? .empty,
layoutDirection: context.layoutDirection
)
let sliderRanges = makeRanges(ranges, with: context)
let sliderModel = SliderModel(
firstThumb: SliderModel.ThumbModel(
block: makeThumbBlock(
thumb: thumbStyle
.makeBlock(context: context, corners: .all),
textBlock: thumbTextStyle?.makeThumbTextBlock(
context: context,
value: firstThumbValue.value
),
textOffset: thumbTextStyle.flatMap {
CGPoint(
x: $0.offset?.x.resolveValue(expressionResolver) ?? 0,
y: $0.offset?.y.resolveValue(expressionResolver) ?? 0
)
} ?? .zero
),
value: firstThumbValue,
size: CGSize(
width: thumbStyle.getWidth(context: context),
height: thumbStyle.getHeight(context: context)
),
offsetX: thumbTextStyle?.offset?.x.resolveValue(expressionResolver) ?? 0,
offsetY: thumbTextStyle?.offset?.y.resolveValue(expressionResolver) ?? 0
),
firstThumb: firstThumb,
secondThumb: secondThumb,
activeMarkModel: tickMarkActiveStyle.flatMap {
SliderModel.MarkModel(
block: $0.makeBlock(context: context, corners: .all),
size: CGSize(
width: $0.getWidth(context: context),
height: $0.getHeight(context: context)
)
)
},
inactiveMarkModel: tickMarkInactiveStyle.flatMap {
SliderModel.MarkModel(
block: $0.makeBlock(context: context, corners: .all),
size: CGSize(
width: $0.getWidth(context: context),
height: $0.getHeight(context: context)
)
)
},
minValue: resolveMinValue(expressionResolver),
maxValue: resolveMaxValue(expressionResolver),
activeTrack: self.trackActiveStyle.makeBlock(
context: context,
widthTrait: .resizable,
corners: .all
),
inactiveTrack: self.trackInactiveStyle.makeBlock(
context: context,
widthTrait: .resizable,
corners: .all
),
minValue: minValue,
maxValue: maxValue,
marksConfiguration: marksConfiguration,
ranges: sliderRanges,
layoutDirection: context.layoutDirection
)
let width = context.override(width: width)
let height = context.override(height: height)
return SliderBlock(
sliderModel: sliderModel,
widthTrait: width.makeLayoutTrait(with: expressionResolver),
heightTrait: height.makeLayoutTrait(with: expressionResolver)
)
}
private func makeRanges(
_ ranges: [DivSlider.Range]?,
with context: DivBlockModelingContext
) -> [SliderModel.RangeModel] {
let expressionResolver = context.expressionResolver
let minValue = resolveMinValue(expressionResolver)
let maxValue = resolveMaxValue(expressionResolver)
var sliderRanges: [SliderModel.RangeModel] = (ranges ?? []).map { range in
SliderModel.RangeModel(
start: range.resolveStart(expressionResolver) ?? minValue,
end: range.resolveEnd(expressionResolver) ?? maxValue,
margins: range.margins.makeEdgeInsets(context: context),
activeTrack: range.trackActiveStyle?.makeBlock(
context: context,
widthTrait: .resizable,
corners: .all
) ?? trackActiveStyle.makeBlock(
context: context,
widthTrait: .resizable,
corners: .all
),
inactiveTrack: range.trackInactiveStyle?.makeBlock(
context: context,
widthTrait: .resizable,
corners: .all
) ?? trackInactiveStyle.makeBlock(
context: context,
widthTrait: .resizable,
corners: .all
)
)
}
let makeBasicRange: (Int, Int, CGRect.Corners) -> SliderModel.RangeModel = { [self] in
SliderModel.RangeModel(
start: $0,
end: $1,
margins: EdgeInsets.zero,
activeTrack: trackActiveStyle.makeBlock(
context: context,
widthTrait: .resizable,
corners: $2
),
inactiveTrack: trackInactiveStyle.makeBlock(
context: context,
widthTrait: .resizable,
corners: $2
)
)
}
sliderRanges.sort { $0.start < $1.start }
var lastRangeEnd = minValue
for (index, range) in sliderRanges.enumerated() {
if range.start != lastRangeEnd {
let corner: CGRect.Corners = index == 0 ? .left : .all
sliderRanges.append(makeBasicRange(lastRangeEnd, range.start, corner))
}
lastRangeEnd = range.end
}
if lastRangeEnd != maxValue {
let corner: CGRect.Corners = sliderRanges.count == 0 ? .all : .right
sliderRanges.append(makeBasicRange(lastRangeEnd, maxValue, corner))
}
sliderRanges.sort { $0.start < $1.start }
return sliderRanges
}
}
private func makeThumbBlock(
@@ -1,7 +1,7 @@
import BaseTinyPublic
import Foundation
public struct MarksConfiguration {
public struct MarksConfiguration: Equatable {
let minValue: CGFloat
let maxValue: CGFloat
var horizontalInset: CGFloat = 0
@@ -33,7 +33,7 @@ public struct MarksConfiguration {
}
extension MarksConfiguration {
public struct RoundedRectangle {
public struct RoundedRectangle: Equatable {
var size: CGSize
let cornerRadius: CGFloat
let color: Color
@@ -62,4 +62,13 @@ extension MarksConfiguration {
borderColor: .clear
)
}
public static func ==(lhs: MarksConfiguration, rhs: MarksConfiguration) -> Bool {
lhs.minValue == rhs.minValue &&
lhs.maxValue == rhs.maxValue &&
lhs.horizontalInset == rhs.horizontalInset &&
lhs.inactiveMark == rhs.inactiveMark &&
lhs.activeMark == rhs.activeMark &&
lhs.layoutDirection == rhs.layoutDirection
}
}
@@ -64,33 +64,63 @@ public struct SliderModel: Equatable {
}
}
public struct RangeModel: Equatable {
public let start: Int
public let end: Int
public let margins: EdgeInsets
public let activeTrack: Block
public let inactiveTrack: Block
public init(
start: Int,
end: Int,
margins: EdgeInsets,
activeTrack: Block,
inactiveTrack: Block
) {
self.start = start
self.end = end
self.margins = margins
self.activeTrack = activeTrack
self.inactiveTrack = inactiveTrack
}
public static func ==(lhs: SliderModel.RangeModel, rhs: SliderModel.RangeModel) -> Bool {
lhs.start == rhs.start && lhs.end == rhs.end && lhs.activeTrack == rhs.activeTrack && lhs
.inactiveTrack == rhs.inactiveTrack && lhs.margins == rhs.margins
}
}
public var firstThumb: ThumbModel
public var secondThumb: ThumbModel?
public let minValue: Int
public let maxValue: Int
public let activeMarkModel: MarkModel?
public let inactiveMarkModel: MarkModel?
public let activeTrack: Block
public let inactiveTrack: Block
public let marksConfiguration: MarksConfiguration
public let ranges: [RangeModel]
public let layoutDirection: UserInterfaceLayoutDirection
public var valueRange: Int {
maxValue - minValue
}
public var sliderHeight: CGFloat {
max(
activeMarkModel?.size.height ?? 0,
inactiveMarkModel?.size.height ?? 0,
marksConfiguration.activeMark.size.height,
marksConfiguration.inactiveMark.size.height,
firstThumb.size.height,
secondThumb?.size.height ?? 0,
activeTrack.intrinsicContentHeight(forWidth: .infinity),
inactiveTrack.intrinsicContentHeight(forWidth: .infinity)
ranges.map { max(
$0.activeTrack.intrinsicContentHeight(forWidth: .infinity),
$0.inactiveTrack.intrinsicContentHeight(forWidth: .infinity)
) }.max() ?? 0
)
}
public var sliderIntrinsicWidth: CGFloat {
let maxMarkWidth = max(
activeMarkModel?.size.width ?? 0,
inactiveMarkModel?.size.width ?? 0
marksConfiguration.activeMark.size.width,
marksConfiguration.inactiveMark.size.width
)
let maxThumbWidth = max(firstThumb.size.width, secondThumb?.size.width ?? 0)
return CGFloat(maxValue - minValue + 1) * maxMarkWidth + max(0, maxThumbWidth - maxMarkWidth)
@@ -106,8 +136,8 @@ public struct SliderModel: Equatable {
public var horizontalInset: CGFloat {
max(
activeMarkModel?.size.width ?? 0,
inactiveMarkModel?.size.width ?? 0,
marksConfiguration.activeMark.size.width,
marksConfiguration.inactiveMark.size.width,
firstThumb.size.width,
secondThumb?.size.width ?? 0
)
@@ -116,24 +146,18 @@ public struct SliderModel: Equatable {
public init(
firstThumb: ThumbModel,
secondThumb: ThumbModel? = nil,
activeMarkModel: MarkModel?,
inactiveMarkModel: MarkModel?,
minValue: Int,
maxValue: Int,
activeTrack: Block,
inactiveTrack: Block,
marksConfiguration: MarksConfiguration,
ranges: [RangeModel],
layoutDirection: UserInterfaceLayoutDirection = .leftToRight
) {
self.firstThumb = firstThumb
self.secondThumb = secondThumb
self.minValue = minValue
self.maxValue = maxValue
self.activeMarkModel = activeMarkModel
self.inactiveMarkModel = inactiveMarkModel
self.activeTrack = activeTrack
self.inactiveTrack = inactiveTrack
self.marksConfiguration = marksConfiguration
self.ranges = ranges
self.layoutDirection = layoutDirection
}
@@ -141,13 +165,10 @@ public struct SliderModel: Equatable {
SliderModel(
firstThumb: .empty,
secondThumb: nil,
activeMarkModel: nil,
inactiveMarkModel: nil,
minValue: 0,
maxValue: 0,
activeTrack: EmptyBlock.zeroSized,
inactiveTrack: EmptyBlock.zeroSized,
marksConfiguration: .empty,
ranges: [],
layoutDirection: .leftToRight
)
}
@@ -157,7 +178,7 @@ public struct SliderModel: Equatable {
lhs.secondThumb == rhs.secondThumb &&
lhs.maxValue == rhs.maxValue &&
lhs.minValue == rhs.minValue &&
lhs.inactiveMarkModel == rhs.inactiveMarkModel &&
lhs.activeMarkModel == rhs.activeMarkModel
lhs.marksConfiguration == rhs.marksConfiguration &&
lhs.ranges == rhs.ranges
}
}
@@ -28,20 +28,20 @@ final class SliderView: BlockView, VisibleBoundsTrackingLeaf {
}
self.sliderModel = sliderModel
inactiveTrack = sliderModel.inactiveTrack.reuse(
inactiveTrack,
inactiveRangeTracks = inactiveRangeTracks.reused(
with: sliderModel.ranges.map { $0.inactiveTrack },
attachTo: inactiveTrackView,
observer: observer,
overscrollDelegate: overscrollDelegate,
renderingDelegate: renderingDelegate,
superview: sliderBackgroundView
renderingDelegate: renderingDelegate
)
activeTrack = sliderModel.activeTrack.reuse(
activeTrack,
activeRangeTracks = activeRangeTracks.reused(
with: sliderModel.ranges.map { $0.activeTrack },
attachTo: activeTrackView,
observer: observer,
overscrollDelegate: overscrollDelegate,
renderingDelegate: renderingDelegate,
superview: sliderBackgroundView
renderingDelegate: renderingDelegate
)
firstThumb = sliderModel.firstThumb.block.reuse(
@@ -113,33 +113,24 @@ final class SliderView: BlockView, VisibleBoundsTrackingLeaf {
}
private var pointWidth: CGFloat {
let stripeCount = sliderModel.maxValue - sliderModel.minValue
let width = bounds.width - sliderModel.horizontalInset - max(
abs(sliderModel.firstThumb.offsetX),
abs(sliderModel.secondThumb?.offsetX ?? 0)
)
if stripeCount == 0 {
if sliderModel.valueRange == 0 {
return width
} else {
return CGFloat(width) / CGFloat(stripeCount)
return CGFloat(width) / CGFloat(sliderModel.valueRange)
}
}
private var firstThumb: BlockView?
private var secondThumb: BlockView?
private var activeTrack: BlockView?
private var inactiveTrack: BlockView?
private lazy var marksView: MarksView = {
let view = MarksView()
self.insertSubview(view, at: 1)
return view
}()
private lazy var sliderBackgroundView: UIView = {
let view = UIView()
self.insertSubview(view, at: 0)
return view
}()
private var activeRangeTracks: [BlockView] = []
private var inactiveRangeTracks: [BlockView] = []
private lazy var inactiveTrackView = makeAndInsertView(UIView(), at: 0)
private lazy var activeTrackView = makeAndInsertView(UIView(), at: 1)
private lazy var marksView = makeAndInsertView(MarksView(), at: 2)
private let recognizer = UILongPressGestureRecognizer()
private var activeThumb: SliderThumbNumber = .first
@@ -157,6 +148,12 @@ final class SliderView: BlockView, VisibleBoundsTrackingLeaf {
fatalError("init(coder:) has not been implemented")
}
private func makeAndInsertView<T: UIView>(_ view: T, at position: Int) -> T {
view.clipsToBounds = true
self.insertSubview(view, at: position)
return view
}
@objc private func handleTap(_ recognizer: UILongPressGestureRecognizer) {
let location = recognizer.location(in: self)
@@ -166,13 +163,13 @@ final class SliderView: BlockView, VisibleBoundsTrackingLeaf {
currentValue = clamp(
(location.x - sliderModel.horizontalInset / 2) / pointWidth,
min: 0.0,
max: CGFloat(sliderModel.maxValue - sliderModel.minValue)
max: CGFloat(sliderModel.valueRange)
) + CGFloat(sliderModel.minValue)
case .rightToLeft:
currentValue = CGFloat(sliderModel.maxValue) - clamp(
(location.x - sliderModel.horizontalInset / 2) / pointWidth,
min: 0.0,
max: CGFloat(sliderModel.maxValue - sliderModel.minValue)
max: CGFloat(sliderModel.valueRange)
)
@unknown default:
assertionFailure("Unknown layoutDirection (UserInterfaceLayoutDirection)")
@@ -285,28 +282,66 @@ final class SliderView: BlockView, VisibleBoundsTrackingLeaf {
)
marksView.configuration.horizontalInset = sliderModel.horizontalInset
marksView.frame = CGRect(
x: 0,
y: 0,
width: bounds
.width - max(
abs(sliderModel.firstThumb.offsetX),
abs(sliderModel.secondThumb?.offsetX ?? 0)
),
height: bounds.height
configureSliderView(marksView)
configureSliderView(inactiveTrackView, with: sliderModel.horizontalInset)
configureSliderView(activeTrackView, with: sliderModel.horizontalInset)
configureRangeViews(
inactiveRangeTracks,
with: sliderModel.ranges,
in: inactiveTrackView,
isActive: false
)
configureRangeViews(
activeRangeTracks,
with: sliderModel.ranges,
in: activeTrackView,
isActive: true
)
sliderBackgroundView.frame = CGRect(
x: 0,
configureTracks(
progressFirstThumb: firstThumbProgress,
progressSecondThumb: sliderModel
.secondThumb != nil ? secondThumbProgress : CGFloat(sliderModel.minValue)
)
}
private func configureRangeViews(
_ rangeViews: [UIView],
with models: [SliderModel.RangeModel],
in parentView: UIView,
isActive: Bool
) {
zip(rangeViews, models).forEach { view, model in
let rangeOfValues = CGFloat(model.end - model.start) / CGFloat(sliderModel.valueRange)
let rangeWidth = parentView.frame
.width * rangeOfValues - (model.margins.left + model.margins.right)
let rangeHeight = isActive ? model.activeTrack
.intrinsicContentHeight(forWidth: rangeWidth) : model.inactiveTrack
.intrinsicContentHeight(forWidth: rangeWidth)
view.frame = CGRect(
x: CGFloat(model.start - sliderModel.minValue) / CGFloat(sliderModel.valueRange) *
inactiveTrackView.frame.width + model.margins.left,
y: (sliderModel.sliderHeight - rangeHeight) / 2,
width: rangeWidth,
height: rangeHeight
)
}
}
private func configureSliderView(_ view: UIView, with horizontalInset: CGFloat = 0) {
view.frame = CGRect(
x: horizontalInset / 2,
y: 0,
width: bounds
.width - max(
abs(sliderModel.firstThumb.offsetX),
abs(sliderModel.secondThumb?.offsetX ?? 0)
),
) - horizontalInset,
height: sliderModel.sliderHeight
)
sliderBackgroundView.center = bounds.center
view.center = bounds.center
.movingX(
by:
(
@@ -319,55 +354,39 @@ final class SliderView: BlockView, VisibleBoundsTrackingLeaf {
-(sliderModel.secondThumb?.offsetX ?? 0) / 2
: (sliderModel.secondThumb?.offsetX ?? 0) / 2
)
configureTracks(
progressFirstThumb: firstThumbProgress,
progressSecondThumb: sliderModel
.secondThumb != nil ? secondThumbProgress : CGFloat(sliderModel.minValue)
)
}
private func configureTracks(
progressFirstThumb: CGFloat,
progressSecondThumb: CGFloat
) {
let makeModifiedSliderViewBounds: (CGFloat) -> CGRect = { [self] trackHeight in
sliderBackgroundView.bounds.insetBy(
dx: sliderModel.horizontalInset / 2,
dy: (
sliderModel.sliderHeight - trackHeight
) / 2
)
}
inactiveTrack?.frame = makeModifiedSliderViewBounds(
sliderModel.inactiveTrack
.intrinsicContentHeight(
forWidth: .infinity
)
)
activeTrack?.frame = modified(makeModifiedSliderViewBounds(
sliderModel.activeTrack
.intrinsicContentHeight(
forWidth: .infinity
)
)) {
let progressFirstThumbWidth =
(min(progressFirstThumb, progressSecondThumb) - CGFloat(sliderModel.minValue)) * pointWidth
let progressSecondThumbWidth =
(max(progressFirstThumb, progressSecondThumb) - CGFloat(sliderModel.minValue)) * pointWidth
let (originX, progressWidth) = {
switch sliderModel.layoutDirection {
case .leftToRight:
$0.origin
.x += (min(progressFirstThumb, progressSecondThumb) - CGFloat(sliderModel.minValue)) *
pointWidth
$0.size.width = abs(progressFirstThumb - progressSecondThumb) * pointWidth
return (progressFirstThumbWidth, progressSecondThumbWidth)
case .rightToLeft:
$0.origin
.x -= (max(progressFirstThumb, progressSecondThumb) - CGFloat(sliderModel.maxValue)) *
pointWidth
$0.size.width = abs(progressFirstThumb - progressSecondThumb) * pointWidth
return (
inactiveTrackView.frame.width - progressSecondThumbWidth,
inactiveTrackView.frame.width - progressFirstThumbWidth
)
@unknown default:
assertionFailure("Unknown layoutDirection (UserInterfaceLayoutDirection)")
return (0, 0)
}
}
}()
activeTrackView.cutView(originX: originX)
activeTrackView.frame = CGRect(
x: inactiveTrackView.frame.minX,
y: inactiveTrackView.frame.minY,
width: progressWidth,
height: sliderModel.sliderHeight
)
marksView.firstThumbProgress = firstThumbProgress
marksView.secondThumbProgress = secondThumbProgress
@@ -431,3 +450,21 @@ private enum ThumbPosition {
}
private let animationDuration = 0.3
extension UIView {
fileprivate func cutView(originX: CGFloat) {
let shapeLayer = CAShapeLayer()
let path = CGMutablePath()
let rect = CGRect(
x: self.bounds.origin.x,
y: self.bounds.origin.y,
width: originX,
height: self.bounds.height
)
path.addRect(rect)
path.addRect(bounds)
shapeLayer.path = path
shapeLayer.fillRule = CAShapeLayerFillRule.evenOdd
layer.mask = shapeLayer
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+4 -1
View File
@@ -131,7 +131,10 @@
},
"minItems": 1,
"$description": "translations.json#/div_slider_ranges",
"platforms": []
"platforms": [
"android",
"ios"
]
},
"secondary_value_accessibility": {
"$ref": "div-accessibility.json",
@@ -1,7 +1,8 @@
{
"description": "Slider with ranges",
"platforms": [
"android"
"android",
"ios"
],
"div_data": {
"templates": {},
@@ -1,7 +1,8 @@
{
"description": "Slider with ranges",
"platforms": [
"android"
"android",
"ios"
],
"templates": {
"test_slider": {