如何在 Swift 中实现范围滑块
How to implement range slider in Swift
我正在尝试实现 Range Slider,我使用了名为 NMRangeSlider 的自定义控件。
但是当我使用它时,滑块根本没有出现。难道也是因为都写在了Objective-C?
我目前是这样实现的:
var rangeSlider = NMRangeSlider(frame: CGRectMake(16, 6, 275, 34))
rangeSlider.lowerValue = 0.54
rangeSlider.upperValue = 0.94
self.view.addSubview(rangeSlider)
试试这个代码:
override func viewDidLayoutSubviews() {
let margin: CGFloat = 20.0
let width = view.bounds.width - 2.0 * margin
rangeSlider.frame = CGRect(x: margin, y: margin + topLayoutGuide.length,
width: width, height: 31.0)
}
更新:
它没有给我看,因为它全是白色的。所以解决方案,不使用任何其他框架并坚持使用这个框架 - 您需要为所有组件设置所有视图,然后它会很好地显示:
我曾尝试在 Swift 中导入它,就像我之前在 Objective-C 代码中使用的那样,但没有成功。如果我正确设置所有内容并将其添加到 viewDidLoad()
或 viewDidAppear()
中,则不会显示任何内容。不过有一件事值得一提——当我输入 View Debug Hierarchy 时,滑块实际上 是 canvas:
但它并没有使用我在将其添加到视图之前设置的所有颜色进行渲染。作为记录 - 这是我使用的代码:
override func viewDidAppear(animated: Bool) {
var rangeSlider = NMRangeSlider(frame: CGRectMake(50, 50, 275, 34))
rangeSlider.lowerValue = 0.54
rangeSlider.upperValue = 0.94
let range = 10.0
let oneStep = 1.0 / range
let minRange: Float = 0.05
rangeSlider.minimumRange = minRange
let bgImage = UIView(frame: rangeSlider.frame)
bgImage.backgroundColor = .greenColor()
rangeSlider.trackImage = bgImage.pb_takeSnapshot()
let trackView = UIView(frame: CGRectMake(0, 0, rangeSlider.frame.size.width, 29))
trackView.backgroundColor = .whiteColor()
trackView.opaque = false
trackView.alpha = 0.3
rangeSlider.trackImage = UIImage(named: "")
let lowerThumb = UIView(frame: CGRectMake(0, 0, 8, 29))
lowerThumb.backgroundColor = .whiteColor()
let lowerThumbHigh = UIView(frame: CGRectMake(0, 0, 8, 29))
lowerThumbHigh.backgroundColor = UIColor.blueColor()
rangeSlider.lowerHandleImageNormal = lowerThumb.pb_takeSnapshot()
rangeSlider.lowerHandleImageHighlighted = lowerThumbHigh.pb_takeSnapshot()
rangeSlider.upperHandleImageNormal = lowerThumb.pb_takeSnapshot()
rangeSlider.upperHandleImageHighlighted = lowerThumbHigh.pb_takeSnapshot()
self.view.addSubview(rangeSlider)
self.view.backgroundColor = .lightGrayColor()
}
使用this question中提到的获取UIView为UIImage的方法:
extension UIView {
func pb_takeSnapshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.mainScreen().scale)
drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
其他解决方案:
您也可以尝试 sgwilly/RangeSlider,它是用 Swift 编写的,因此您甚至不需要桥接头。
我使用以下方法实现了范围滑块:
https://github.com/Zengzhihui/RangeSlider
在 GZRangeSlider class 中,有一个方法叫做:
私有函数 setLabelText()
在那个方法中,只需输入:
leftTextLayer.frame = CGRectMake(leftHandleLayer.frame.minX - 0.5 * (kTextWidth - leftHandleLayer.frame.width), leftHandleLayer.frame.minY - kTextHeight, kTextWidth, kTextHeight)
rightTextLayer.frame = CGRectMake(rightHandleLayer.frame.minX - 0.5 * (kTextWidth - leftHandleLayer.frame.width), leftTextLayer.frame.minY, kTextWidth, kTextHeight)
为上下标签设置动画..
这个对我来说效果很好,它在 swift.. 试一试..
要创建自定义范围滑块,我在这里找到了一个很好的解决方案:range finder tutorial iOS 8 但我的项目在 swift 3 中需要这个。我在这里更新了 Swift 3 iOS 10:
在您的主视图控制器中将其添加到 viewDidLayOut 以显示范围滑块。
override func viewDidLayoutSubviews() {
let margin: CGFloat = 20.0
let width = view.bounds.width - 2.0 * margin
rangeSlider.frame = CGRect(x: margin, y: margin + topLayoutGuide.length + 170, width: width, height: 31.0)
}
创建辅助函数以在 viewDidLayoutSubviews() 下面打印滑块输出
func rangeSliderValueChanged() { //rangeSlider: RangeSlider
print("Range slider value changed: \(rangeSlider.lowerValue) \(rangeSlider.upperValue) ")//(\(rangeSlider.lowerValue) \(rangeSlider.upperValue))
}
创建文件 RangeSlider.swift 并将其添加到其中:
import UIKit
import QuartzCore
class RangeSlider: UIControl {
var minimumValue = 0.0
var maximumValue = 1.0
var lowerValue = 0.2
var upperValue = 0.8
let trackLayer = RangeSliderTrackLayer()//= CALayer() defined in RangeSliderTrackLayer.swift
let lowerThumbLayer = RangeSliderThumbLayer()//CALayer()
let upperThumbLayer = RangeSliderThumbLayer()//CALayer()
var previousLocation = CGPoint()
var trackTintColor = UIColor(white: 0.9, alpha: 1.0)
var trackHighlightTintColor = UIColor(red: 0.0, green: 0.45, blue: 0.94, alpha: 1.0)
var thumbTintColor = UIColor.white
var curvaceousness : CGFloat = 1.0
var thumbWidth: CGFloat {
return CGFloat(bounds.height)
}
override init(frame: CGRect) {
super.init(frame: frame)
trackLayer.rangeSlider = self
trackLayer.contentsScale = UIScreen.main.scale
layer.addSublayer(trackLayer)
lowerThumbLayer.rangeSlider = self
lowerThumbLayer.contentsScale = UIScreen.main.scale
layer.addSublayer(lowerThumbLayer)
upperThumbLayer.rangeSlider = self
upperThumbLayer.contentsScale = UIScreen.main.scale
layer.addSublayer(upperThumbLayer)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func updateLayerFrames() {
trackLayer.frame = bounds.insetBy(dx: 0.0, dy: bounds.height / 3)
trackLayer.setNeedsDisplay()
let lowerThumbCenter = CGFloat(positionForValue(value: lowerValue))
lowerThumbLayer.frame = CGRect(x: lowerThumbCenter - thumbWidth / 2.0, y: 0.0,
width: thumbWidth, height: thumbWidth)
lowerThumbLayer.setNeedsDisplay()
let upperThumbCenter = CGFloat(positionForValue(value: upperValue))
upperThumbLayer.frame = CGRect(x: upperThumbCenter - thumbWidth / 2.0, y: 0.0,
width: thumbWidth, height: thumbWidth)
upperThumbLayer.setNeedsDisplay()
}
func positionForValue(value: Double) -> Double {
return Double(bounds.width - thumbWidth) * (value - minimumValue) /
(maximumValue - minimumValue) + Double(thumbWidth / 2.0)
}
override var frame: CGRect {
didSet {
updateLayerFrames()
}
}
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
previousLocation = touch.location(in: self)
// Hit test the thumb layers
if lowerThumbLayer.frame.contains(previousLocation) {
lowerThumbLayer.highlighted = true
} else if upperThumbLayer.frame.contains(previousLocation) {
upperThumbLayer.highlighted = true
}
return lowerThumbLayer.highlighted || upperThumbLayer.highlighted
}
func boundValue(value: Double, toLowerValue lowerValue: Double, upperValue: Double) -> Double {
return min(max(value, lowerValue), upperValue)
}
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let location = touch.location(in: self)
// 1. Determine by how much the user has dragged
let deltaLocation = Double(location.x - previousLocation.x)
let deltaValue = (maximumValue - minimumValue) * deltaLocation / Double(bounds.width - thumbWidth)
previousLocation = location
// 2. Update the values
if lowerThumbLayer.highlighted {
lowerValue += deltaValue
lowerValue = boundValue(value: lowerValue, toLowerValue: minimumValue, upperValue: upperValue)
} else if upperThumbLayer.highlighted {
upperValue += deltaValue
upperValue = boundValue(value: upperValue, toLowerValue: lowerValue, upperValue: maximumValue)
}
// 3. Update the UI
CATransaction.begin()
CATransaction.setDisableActions(true)
updateLayerFrames()
CATransaction.commit()
sendActions(for: .valueChanged)
return true
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
lowerThumbLayer.highlighted = false
upperThumbLayer.highlighted = false
}
}
接下来添加缩略图图层子类文件RangeSliderThumbLayer.swift并在其中添加:
import UIKit
class RangeSliderThumbLayer: CALayer {
var highlighted = false
weak var rangeSlider: RangeSlider?
override func draw(in ctx: CGContext) {
if let slider = rangeSlider {
let thumbFrame = bounds.insetBy(dx: 2.0, dy: 2.0)
let cornerRadius = thumbFrame.height * slider.curvaceousness / 2.0
let thumbPath = UIBezierPath(roundedRect: thumbFrame, cornerRadius: cornerRadius)
// Fill - with a subtle shadow
let shadowColor = UIColor.gray
ctx.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 1.0, color: shadowColor.cgColor)
ctx.setFillColor(slider.thumbTintColor.cgColor)
ctx.addPath(thumbPath.cgPath)
ctx.fillPath()
// Outline
ctx.setStrokeColor(shadowColor.cgColor)
ctx.setLineWidth(0.5)
ctx.addPath(thumbPath.cgPath)
ctx.strokePath()
if highlighted {
ctx.setFillColor(UIColor(white: 0.0, alpha: 0.1).cgColor)
ctx.addPath(thumbPath.cgPath)
ctx.fillPath()
}
}
}
}
最后添加track layer子类文件RangeSliderTrackLayer.swift并添加如下内容:
import Foundation
import UIKit
import QuartzCore
class RangeSliderTrackLayer: CALayer {
weak var rangeSlider: RangeSlider?
override func draw(in ctx: CGContext) {
if let slider = rangeSlider {
// Clip
let cornerRadius = bounds.height * slider.curvaceousness / 2.0
let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
ctx.addPath(path.cgPath)
// Fill the track
ctx.setFillColor(slider.trackTintColor.cgColor)
ctx.addPath(path.cgPath)
ctx.fillPath()
// Fill the highlighted range
ctx.setFillColor(slider.trackHighlightTintColor.cgColor)
let lowerValuePosition = CGFloat(slider.positionForValue(value: slider.lowerValue))
let upperValuePosition = CGFloat(slider.positionForValue(value: slider.upperValue))
let rect = CGRect(x: lowerValuePosition, y: 0.0, width: upperValuePosition - lowerValuePosition, height: bounds.height)
ctx.fill(rect)
}
}
}
构建 运行 并获取:
我正在尝试实现 Range Slider,我使用了名为 NMRangeSlider 的自定义控件。
但是当我使用它时,滑块根本没有出现。难道也是因为都写在了Objective-C?
我目前是这样实现的:
var rangeSlider = NMRangeSlider(frame: CGRectMake(16, 6, 275, 34))
rangeSlider.lowerValue = 0.54
rangeSlider.upperValue = 0.94
self.view.addSubview(rangeSlider)
试试这个代码:
override func viewDidLayoutSubviews() {
let margin: CGFloat = 20.0
let width = view.bounds.width - 2.0 * margin
rangeSlider.frame = CGRect(x: margin, y: margin + topLayoutGuide.length,
width: width, height: 31.0)
}
更新:
它没有给我看,因为它全是白色的。所以解决方案,不使用任何其他框架并坚持使用这个框架 - 您需要为所有组件设置所有视图,然后它会很好地显示:
我曾尝试在 Swift 中导入它,就像我之前在 Objective-C 代码中使用的那样,但没有成功。如果我正确设置所有内容并将其添加到 viewDidLoad()
或 viewDidAppear()
中,则不会显示任何内容。不过有一件事值得一提——当我输入 View Debug Hierarchy 时,滑块实际上 是 canvas:
但它并没有使用我在将其添加到视图之前设置的所有颜色进行渲染。作为记录 - 这是我使用的代码:
override func viewDidAppear(animated: Bool) {
var rangeSlider = NMRangeSlider(frame: CGRectMake(50, 50, 275, 34))
rangeSlider.lowerValue = 0.54
rangeSlider.upperValue = 0.94
let range = 10.0
let oneStep = 1.0 / range
let minRange: Float = 0.05
rangeSlider.minimumRange = minRange
let bgImage = UIView(frame: rangeSlider.frame)
bgImage.backgroundColor = .greenColor()
rangeSlider.trackImage = bgImage.pb_takeSnapshot()
let trackView = UIView(frame: CGRectMake(0, 0, rangeSlider.frame.size.width, 29))
trackView.backgroundColor = .whiteColor()
trackView.opaque = false
trackView.alpha = 0.3
rangeSlider.trackImage = UIImage(named: "")
let lowerThumb = UIView(frame: CGRectMake(0, 0, 8, 29))
lowerThumb.backgroundColor = .whiteColor()
let lowerThumbHigh = UIView(frame: CGRectMake(0, 0, 8, 29))
lowerThumbHigh.backgroundColor = UIColor.blueColor()
rangeSlider.lowerHandleImageNormal = lowerThumb.pb_takeSnapshot()
rangeSlider.lowerHandleImageHighlighted = lowerThumbHigh.pb_takeSnapshot()
rangeSlider.upperHandleImageNormal = lowerThumb.pb_takeSnapshot()
rangeSlider.upperHandleImageHighlighted = lowerThumbHigh.pb_takeSnapshot()
self.view.addSubview(rangeSlider)
self.view.backgroundColor = .lightGrayColor()
}
使用this question中提到的获取UIView为UIImage的方法:
extension UIView {
func pb_takeSnapshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.mainScreen().scale)
drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
其他解决方案:
您也可以尝试 sgwilly/RangeSlider,它是用 Swift 编写的,因此您甚至不需要桥接头。
我使用以下方法实现了范围滑块: https://github.com/Zengzhihui/RangeSlider
在 GZRangeSlider class 中,有一个方法叫做: 私有函数 setLabelText()
在那个方法中,只需输入:
leftTextLayer.frame = CGRectMake(leftHandleLayer.frame.minX - 0.5 * (kTextWidth - leftHandleLayer.frame.width), leftHandleLayer.frame.minY - kTextHeight, kTextWidth, kTextHeight)
rightTextLayer.frame = CGRectMake(rightHandleLayer.frame.minX - 0.5 * (kTextWidth - leftHandleLayer.frame.width), leftTextLayer.frame.minY, kTextWidth, kTextHeight)
为上下标签设置动画..
这个对我来说效果很好,它在 swift.. 试一试..
要创建自定义范围滑块,我在这里找到了一个很好的解决方案:range finder tutorial iOS 8 但我的项目在 swift 3 中需要这个。我在这里更新了 Swift 3 iOS 10:
在您的主视图控制器中将其添加到 viewDidLayOut 以显示范围滑块。
override func viewDidLayoutSubviews() { let margin: CGFloat = 20.0 let width = view.bounds.width - 2.0 * margin rangeSlider.frame = CGRect(x: margin, y: margin + topLayoutGuide.length + 170, width: width, height: 31.0) }
创建辅助函数以在 viewDidLayoutSubviews() 下面打印滑块输出
func rangeSliderValueChanged() { //rangeSlider: RangeSlider print("Range slider value changed: \(rangeSlider.lowerValue) \(rangeSlider.upperValue) ")//(\(rangeSlider.lowerValue) \(rangeSlider.upperValue)) }
创建文件 RangeSlider.swift 并将其添加到其中:
import UIKit import QuartzCore class RangeSlider: UIControl { var minimumValue = 0.0 var maximumValue = 1.0 var lowerValue = 0.2 var upperValue = 0.8 let trackLayer = RangeSliderTrackLayer()//= CALayer() defined in RangeSliderTrackLayer.swift let lowerThumbLayer = RangeSliderThumbLayer()//CALayer() let upperThumbLayer = RangeSliderThumbLayer()//CALayer() var previousLocation = CGPoint() var trackTintColor = UIColor(white: 0.9, alpha: 1.0) var trackHighlightTintColor = UIColor(red: 0.0, green: 0.45, blue: 0.94, alpha: 1.0) var thumbTintColor = UIColor.white var curvaceousness : CGFloat = 1.0 var thumbWidth: CGFloat { return CGFloat(bounds.height) } override init(frame: CGRect) { super.init(frame: frame) trackLayer.rangeSlider = self trackLayer.contentsScale = UIScreen.main.scale layer.addSublayer(trackLayer) lowerThumbLayer.rangeSlider = self lowerThumbLayer.contentsScale = UIScreen.main.scale layer.addSublayer(lowerThumbLayer) upperThumbLayer.rangeSlider = self upperThumbLayer.contentsScale = UIScreen.main.scale layer.addSublayer(upperThumbLayer) } required init?(coder: NSCoder) { super.init(coder: coder) } func updateLayerFrames() { trackLayer.frame = bounds.insetBy(dx: 0.0, dy: bounds.height / 3) trackLayer.setNeedsDisplay() let lowerThumbCenter = CGFloat(positionForValue(value: lowerValue)) lowerThumbLayer.frame = CGRect(x: lowerThumbCenter - thumbWidth / 2.0, y: 0.0, width: thumbWidth, height: thumbWidth) lowerThumbLayer.setNeedsDisplay() let upperThumbCenter = CGFloat(positionForValue(value: upperValue)) upperThumbLayer.frame = CGRect(x: upperThumbCenter - thumbWidth / 2.0, y: 0.0, width: thumbWidth, height: thumbWidth) upperThumbLayer.setNeedsDisplay() } func positionForValue(value: Double) -> Double { return Double(bounds.width - thumbWidth) * (value - minimumValue) / (maximumValue - minimumValue) + Double(thumbWidth / 2.0) } override var frame: CGRect { didSet { updateLayerFrames() } } override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { previousLocation = touch.location(in: self) // Hit test the thumb layers if lowerThumbLayer.frame.contains(previousLocation) { lowerThumbLayer.highlighted = true } else if upperThumbLayer.frame.contains(previousLocation) { upperThumbLayer.highlighted = true } return lowerThumbLayer.highlighted || upperThumbLayer.highlighted } func boundValue(value: Double, toLowerValue lowerValue: Double, upperValue: Double) -> Double { return min(max(value, lowerValue), upperValue) } override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { let location = touch.location(in: self) // 1. Determine by how much the user has dragged let deltaLocation = Double(location.x - previousLocation.x) let deltaValue = (maximumValue - minimumValue) * deltaLocation / Double(bounds.width - thumbWidth) previousLocation = location // 2. Update the values if lowerThumbLayer.highlighted { lowerValue += deltaValue lowerValue = boundValue(value: lowerValue, toLowerValue: minimumValue, upperValue: upperValue) } else if upperThumbLayer.highlighted { upperValue += deltaValue upperValue = boundValue(value: upperValue, toLowerValue: lowerValue, upperValue: maximumValue) } // 3. Update the UI CATransaction.begin() CATransaction.setDisableActions(true) updateLayerFrames() CATransaction.commit() sendActions(for: .valueChanged) return true } override func endTracking(_ touch: UITouch?, with event: UIEvent?) { lowerThumbLayer.highlighted = false upperThumbLayer.highlighted = false } }
接下来添加缩略图图层子类文件RangeSliderThumbLayer.swift并在其中添加:
import UIKit class RangeSliderThumbLayer: CALayer { var highlighted = false weak var rangeSlider: RangeSlider? override func draw(in ctx: CGContext) { if let slider = rangeSlider { let thumbFrame = bounds.insetBy(dx: 2.0, dy: 2.0) let cornerRadius = thumbFrame.height * slider.curvaceousness / 2.0 let thumbPath = UIBezierPath(roundedRect: thumbFrame, cornerRadius: cornerRadius) // Fill - with a subtle shadow let shadowColor = UIColor.gray ctx.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 1.0, color: shadowColor.cgColor) ctx.setFillColor(slider.thumbTintColor.cgColor) ctx.addPath(thumbPath.cgPath) ctx.fillPath() // Outline ctx.setStrokeColor(shadowColor.cgColor) ctx.setLineWidth(0.5) ctx.addPath(thumbPath.cgPath) ctx.strokePath() if highlighted { ctx.setFillColor(UIColor(white: 0.0, alpha: 0.1).cgColor) ctx.addPath(thumbPath.cgPath) ctx.fillPath() } } } }
最后添加track layer子类文件RangeSliderTrackLayer.swift并添加如下内容:
import Foundation import UIKit import QuartzCore class RangeSliderTrackLayer: CALayer { weak var rangeSlider: RangeSlider? override func draw(in ctx: CGContext) { if let slider = rangeSlider { // Clip let cornerRadius = bounds.height * slider.curvaceousness / 2.0 let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius) ctx.addPath(path.cgPath) // Fill the track ctx.setFillColor(slider.trackTintColor.cgColor) ctx.addPath(path.cgPath) ctx.fillPath() // Fill the highlighted range ctx.setFillColor(slider.trackHighlightTintColor.cgColor) let lowerValuePosition = CGFloat(slider.positionForValue(value: slider.lowerValue)) let upperValuePosition = CGFloat(slider.positionForValue(value: slider.upperValue)) let rect = CGRect(x: lowerValuePosition, y: 0.0, width: upperValuePosition - lowerValuePosition, height: bounds.height) ctx.fill(rect) } } }
构建 运行 并获取: