如何在 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:

  1. 在您的主视图控制器中将其添加到 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)
    }
    
  2. 创建辅助函数以在 viewDidLayoutSubviews() 下面打印滑块输出

    func rangeSliderValueChanged() { //rangeSlider: RangeSlider
        print("Range slider value changed: \(rangeSlider.lowerValue) \(rangeSlider.upperValue) ")//(\(rangeSlider.lowerValue) \(rangeSlider.upperValue))
    }
    
  3. 创建文件 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
      }
    }
    
  4. 接下来添加缩略图图层子类文件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()
        }
      }
     }
    }
    
  5. 最后添加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)
       }
      }
     }
    

构建 运行 并获取: