【问题标题】:How to implement range slider in Swift如何在 Swift 中实现范围滑块
【发布时间】:2015-09-19 16:48:36
【问题描述】:

我正在尝试实现 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)

【问题讨论】:

  • 你能展示一些你是如何使用它的代码吗?我以前用过这个没问题。
  • 是的,当然。我正在尝试像这样以编程方式添加它: var rangeSlider = NMRangeSlider(frame: CGRectMake(16, 6, 275, 34)) rangeSlider.lowerValue = 0.54 rangeSlider.upperValue = 0.94 self.view.addSubview(rangeSlider)
  • @Michal 对不起,当我看到演示时,我注意到:为了实现范围滑块,我们所做的只是让视图类是 NMRangeSlider。正确的 ?你是否使用过这个框架 swift ?或 Obj-C
  • 立即查看我的答案 - 您需要设置滑块组件的所有视图。然后它将起作用。 (经过测试)

标签: ios objective-c swift uicontrol


【解决方案1】:

要创建自定义范围滑块,我在这里找到了一个很好的解决方案: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. 最后添加轨道图层子类文件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)
       }
      }
     }
    

构建运行并获取:

【讨论】:

    【解决方案2】:

    我使用以下方法实现了范围滑块: https://github.com/Zengzhihui/RangeSlider

    在 GZRangeSlider 类中,有一个方法叫做: 私有函数 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)

    为上下标签设置动画..

    这个对我来说效果很好,而且速度很快..试试吧..

    【讨论】:

      【解决方案3】:

      更新:

      它没有显示给我,因为它全是白色的。所以解决方案,不使用任何其他框架并坚持使用这个 - 您需要为所有组件设置所有视图,然后它会很好地显示:


      我尝试在 Swift 中导入它,就像我之前在 Objective-C 代码中使用它一样,但没有任何运气。如果我正确设置所有内容并将其添加到viewDidLoad()viewDidAppear(),则不会显示任何内容。不过,值得一提的是 - 当我进入 View Debug Hierarchy 时,滑块实际上 在画布上:

      但它根本没有使用我在将其添加到视图之前设置的所有颜色进行渲染。作为记录 - 这是我使用的代码:

      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 编写的,因此您甚至不需要桥接头。

      【讨论】:

      • 尝试输入Debug View Hierarchy,看看它是否在画布上。
      • 它是透明的(不可见的)还是白色的还是看起来像什么?
      • 滑块的所有组件都是白色的!!它似乎没有采用滑块提供的默认值!!!
      • 我想知道为什么会这样!!在 NMRangSlider 的演示中,只需将视图的类更改为 NMRangeSlide,然后一切正常!我真的很难过:|
      • 如果您复制并粘贴我在答案中输入的代码,它们不应该是白色的。
      【解决方案4】:

      试试这个代码:

      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)
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-01-21
        • 1970-01-01
        • 1970-01-01
        • 2019-02-24
        • 1970-01-01
        • 1970-01-01
        • 2018-11-21
        相关资源
        最近更新 更多