How to implement range slider in Swift
Asked Answered
B

4

8

I'm trying to implement Range Slider and I used custom control called NMRangeSlider.

But when I use it, the slider doesn't appear at all. Could it be also because it's all written in Objective-C?

This is how I've currently implemented it:

var rangeSlider = NMRangeSlider(frame: CGRectMake(16, 6, 275, 34))
rangeSlider.lowerValue = 0.54
rangeSlider.upperValue = 0.94
self.view.addSubview(rangeSlider)
Businessman answered 2/7, 2015 at 8:48 Comment(4)
Could you show some code how you're using it? I've used this before with no problems.Seringa
yes for sure. I'm trying to add it programmatically like this: var rangeSlider = NMRangeSlider(frame: CGRectMake(16, 6, 275, 34)) rangeSlider.lowerValue = 0.54 rangeSlider.upperValue = 0.94 self.view.addSubview(rangeSlider)Businessman
@Seringa excuse me, when i saw the demo, i noticed : to implement the range slider what we do is just to make the view class is NMRangeSlider. Right ? and did you use this framework with swift ? or Obj-CBusinessman
See my answer now - you need to set all the views for the components of the slider. Then it will work. (tested)Seringa
S
2

UPDATE:

It did not show to me, because it was all white. So the solution, without using any other framework and sticking with this one - you need to set all the views for all the components and then it will display well:

enter image description here


I have tried to import it in Swift as I used it before in Objective-C code, but without any luck. If I set everything properly and add it either in viewDidLoad() or viewDidAppear(), nothing gets displayed. One thing is worth mentioning, though - when I enter View Debug Hierarchy, the slider actually is there on the canvas:

enter image description here

But it's simply not rendered with all the colors that I did set before adding in it to the view. For the record - this is the code I used:

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

Using the method for capturing the UIView as UIImage mentioned in this question:

extension UIView {
    func pb_takeSnapshot() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.mainScreen().scale)
        drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
}

Other solution:

You can also try sgwilly/RangeSlider instead, it's written in Swift and therefore you won't even need a Bridging Header.

Seringa answered 2/7, 2015 at 9:24 Comment(11)
Try entering the Debug View Hierarchy and see if it's on canvas.Seringa
And is it all see through (invisible) or white or what does it look like?Seringa
all the components of the slider are white !! it seems that it didn't take the default values that the slider provide it!!!Businessman
i wander why that happen !! in the demo of NMRangSlider, the just change the class of the view to NMRangeSlide, then alll it works !! i feel so bad really :|Businessman
They shouldn't be white if you copy and paste my code I put in my answer.Seringa
Also try changing the color of your views background (self.view.backgroundColor = .grayColor() for example) and maybe you'll see it where it is.Seringa
this is a snapshot for it : cloud.githubusercontent.com/assets/12219139/8475614/…Businessman
My code does not create that - try the code that is in my answer (it changed)Seringa
yeah i tried the new code, the slider appear with two white thumb, but the trakImage doesn't appear even if i assign an image to it. but can you tell me why if i hint this code: rangeSlider.lowerHandleImageNormal = lowerThumb.pb_takeSnapshot() rangeSlider.lowerHandleImageHighlighted = lowerThumbHigh.pb_takeSnapshot() rangeSlider.upperHandleImageNormal = lowerThumb.pb_takeSnapshot() rangeSlider.upperHandleImageHighlighted = lowerThumbHigh.pb_takeSnapshot() the slider will disappear again ?!Businessman
My bad, the CGContext doesn't like the blackColor, try changing this line bgImage.backgroundColor = .blackColor() to .greenColor() to see if it works.Seringa
It disappears because it does not know what to display, since you're not assigning it. There are no default values - you have to set everything yourself. If nothing happened, then you're doing something different. I'm building the code AS-IS in Xcode 6.4, iPhone 6 Simulator against SDK 8.4 and I'm getting the slider with green background.Seringa
D
6

To create a custom Range Slider I found a good solution here: range finder tutorial iOS 8 but I needed this in swift 3 for my project. I updated this for Swift 3 iOS 10 here:

  1. in your main view controller add this to viewDidLayOut to show a range slider.

    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. create the helper function to print slider output below viewDidLayoutSubviews()

    func rangeSliderValueChanged() { //rangeSlider: RangeSlider
        print("Range slider value changed: \(rangeSlider.lowerValue) \(rangeSlider.upperValue) ")//(\(rangeSlider.lowerValue) \(rangeSlider.upperValue))
    }
    
  3. Create the file RangeSlider.swift and add this to it:

    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. Next add the thumb layer subclass file RangeSliderThumbLayer.swift and add this to it:

      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. Finally add the track layer subclass file RangeSliderTrackLayer.swift and add the following to it:

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

Build Run and Get: enter image description here

Dingy answered 7/2, 2017 at 22:43 Comment(2)
in the viewDidLoad you should insert rangeSlider.addTarget(self, action: #selector(rangeSliderValueChanged), for: .valueChanged)so that moves of the cursors are detectedInstallment
Nice solution. When used in a modalView, however, I had cross-talks between the rangeSlider and the gestureRecognizer managing the dismissal of the modalView, so that I had to temporarily inhibit the latter, following this answer https://mcmap.net/q/179645/-disable-gesture-to-pull-down-form-page-sheet-modal-presentation and my comment to it.Installment
S
2

UPDATE:

It did not show to me, because it was all white. So the solution, without using any other framework and sticking with this one - you need to set all the views for all the components and then it will display well:

enter image description here


I have tried to import it in Swift as I used it before in Objective-C code, but without any luck. If I set everything properly and add it either in viewDidLoad() or viewDidAppear(), nothing gets displayed. One thing is worth mentioning, though - when I enter View Debug Hierarchy, the slider actually is there on the canvas:

enter image description here

But it's simply not rendered with all the colors that I did set before adding in it to the view. For the record - this is the code I used:

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

Using the method for capturing the UIView as UIImage mentioned in this question:

extension UIView {
    func pb_takeSnapshot() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.mainScreen().scale)
        drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
}

Other solution:

You can also try sgwilly/RangeSlider instead, it's written in Swift and therefore you won't even need a Bridging Header.

Seringa answered 2/7, 2015 at 9:24 Comment(11)
Try entering the Debug View Hierarchy and see if it's on canvas.Seringa
And is it all see through (invisible) or white or what does it look like?Seringa
all the components of the slider are white !! it seems that it didn't take the default values that the slider provide it!!!Businessman
i wander why that happen !! in the demo of NMRangSlider, the just change the class of the view to NMRangeSlide, then alll it works !! i feel so bad really :|Businessman
They shouldn't be white if you copy and paste my code I put in my answer.Seringa
Also try changing the color of your views background (self.view.backgroundColor = .grayColor() for example) and maybe you'll see it where it is.Seringa
this is a snapshot for it : cloud.githubusercontent.com/assets/12219139/8475614/…Businessman
My code does not create that - try the code that is in my answer (it changed)Seringa
yeah i tried the new code, the slider appear with two white thumb, but the trakImage doesn't appear even if i assign an image to it. but can you tell me why if i hint this code: rangeSlider.lowerHandleImageNormal = lowerThumb.pb_takeSnapshot() rangeSlider.lowerHandleImageHighlighted = lowerThumbHigh.pb_takeSnapshot() rangeSlider.upperHandleImageNormal = lowerThumb.pb_takeSnapshot() rangeSlider.upperHandleImageHighlighted = lowerThumbHigh.pb_takeSnapshot() the slider will disappear again ?!Businessman
My bad, the CGContext doesn't like the blackColor, try changing this line bgImage.backgroundColor = .blackColor() to .greenColor() to see if it works.Seringa
It disappears because it does not know what to display, since you're not assigning it. There are no default values - you have to set everything yourself. If nothing happened, then you're doing something different. I'm building the code AS-IS in Xcode 6.4, iPhone 6 Simulator against SDK 8.4 and I'm getting the slider with green background.Seringa
T
0

try this code :

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)
}
Tommie answered 2/7, 2015 at 9:0 Comment(0)
D
0

I implemented the range slider using : https://github.com/Zengzhihui/RangeSlider

In the GZRangeSlider class, there is a method called : private func setLabelText()

In that method, just put :

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)

to animate the lower and upper labels..

This one is working well for me and its in swift.. just try it..

Dodiedodo answered 21/10, 2016 at 14:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.