美文网首页
UIKit框架(六) —— 自定义控件:可重复使用的滑块(二)

UIKit框架(六) —— 自定义控件:可重复使用的滑块(二)

作者: 刀客传奇 | 来源:发表于2018-11-19 15:27 被阅读108次

版本记录

版本号 时间
V1.0 2018.11.19 星期一

前言

iOS中有关视图控件用户能看到的都在UIKit框架里面,用户交互也是通过UIKit进行的。感兴趣的参考上面几篇文章。
1. UIKit框架(一) —— UIKit动力学和移动效果(一)
2. UIKit框架(二) —— UIKit动力学和移动效果(二)
3. UIKit框架(三) —— UICollectionViewCell的扩张效果的实现(一)
4. UIKit框架(四) —— UICollectionViewCell的扩张效果的实现(二)
5. UIKit框架(五) —— 自定义控件:可重复使用的滑块(一)

源码

1. Swift

下面看一下源码

1. ViewController.swift
import UIKit

class ViewController: UIViewController {
  let rangeSlider = RangeSlider(frame: .zero)
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    view.addSubview(rangeSlider)
    rangeSlider.addTarget(self, action: #selector(rangeSliderValueChanged(_:)),
                          for: .valueChanged)
    let time = DispatchTime.now() + 1
    DispatchQueue.main.asyncAfter(deadline: time) {
      self.rangeSlider.trackHighlightTintColor = .red
      self.rangeSlider.thumbImage = #imageLiteral(resourceName: "RectThumb")
      self.rangeSlider.highlightedThumbImage = #imageLiteral(resourceName: "HighlightedRect")
    }
  }
  
  override func viewDidLayoutSubviews() {
    let margin: CGFloat = 20
    let width = view.bounds.width - 2 * margin
    let height: CGFloat = 30
    
    rangeSlider.frame = CGRect(x: 0, y: 0,
                               width: width, height: height)
    rangeSlider.center = view.center
  }
  
  @objc func rangeSliderValueChanged(_ rangeSlider: RangeSlider) {
    let values = "(\(rangeSlider.lowerValue) \(rangeSlider.upperValue))"
    print("Range slider value changed: \(values)")
  }
}
2. RangeSliderTrackLayer.swift
import UIKit

class RangeSliderTrackLayer: CALayer {
  weak var rangeSlider: RangeSlider?
  
  override func draw(in ctx: CGContext) {
    guard let slider = rangeSlider else {
      return
    }
    
    let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
    ctx.addPath(path.cgPath)
    
    ctx.setFillColor(slider.trackTintColor.cgColor)
    ctx.fillPath()
    
    ctx.setFillColor(slider.trackHighlightTintColor.cgColor)
    let lowerValuePosition = slider.positionForValue(slider.lowerValue)
    let upperValuePosition = slider.positionForValue(slider.upperValue)
    let rect = CGRect(x: lowerValuePosition, y: 0,
                      width: upperValuePosition - lowerValuePosition,
                      height: bounds.height)
    ctx.fill(rect)
  }
}
3. RangeSlider.swift
import UIKit

class RangeSlider: UIControl {
  override var frame: CGRect {
    didSet {
      updateLayerFrames()
    }
  }
  
  var minimumValue: CGFloat = 0 {
    didSet {
      updateLayerFrames()
    }
  }
  
  var maximumValue: CGFloat = 1 {
    didSet {
      updateLayerFrames()
    }
  }
  
  var lowerValue: CGFloat = 0.2 {
    didSet {
      updateLayerFrames()
    }
  }
  
  var upperValue: CGFloat = 0.8 {
    didSet {
      updateLayerFrames()
    }
  }
  
  var trackTintColor = UIColor(white: 0.9, alpha: 1) {
    didSet {
      trackLayer.setNeedsDisplay()
    }
  }
  
  var trackHighlightTintColor = UIColor(red: 0, green: 0.45, blue: 0.94, alpha: 1) {
    didSet {
      trackLayer.setNeedsDisplay()
    }
  }
  
  var thumbImage = #imageLiteral(resourceName: "Oval") {
    didSet {
      upperThumbImageView.image = thumbImage
      lowerThumbImageView.image = thumbImage
      updateLayerFrames()
    }
  }
  
  var highlightedThumbImage = #imageLiteral(resourceName: "HighlightedOval") {
    didSet {
      upperThumbImageView.highlightedImage = highlightedThumbImage
      lowerThumbImageView.highlightedImage = highlightedThumbImage
      updateLayerFrames()
    }
  }
  
  
  private let trackLayer = RangeSliderTrackLayer()
  private let lowerThumbImageView = UIImageView()
  private let upperThumbImageView = UIImageView()
  private var previousLocation = CGPoint()
  
  override init(frame: CGRect) {
    super.init(frame: frame)
    
    trackLayer.rangeSlider = self
    trackLayer.contentsScale = UIScreen.main.scale
    layer.addSublayer(trackLayer)
    
    lowerThumbImageView.image = thumbImage
    addSubview(lowerThumbImageView)
    
    upperThumbImageView.image = thumbImage
    addSubview(upperThumbImageView)
  }

  
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  // 1
  private func updateLayerFrames() {
    CATransaction.begin()
    CATransaction.setDisableActions(true)

    trackLayer.frame = bounds.insetBy(dx: 0.0, dy: bounds.height / 3)
    trackLayer.setNeedsDisplay()
    lowerThumbImageView.frame = CGRect(origin: thumbOriginForValue(lowerValue),
                                       size: thumbImage.size)
    upperThumbImageView.frame = CGRect(origin: thumbOriginForValue(upperValue),
                                       size: thumbImage.size)
    CATransaction.commit()
  }
  // 2
  func positionForValue(_ value: CGFloat) -> CGFloat {
    return bounds.width * value
  }
  // 3
  private func thumbOriginForValue(_ value: CGFloat) -> CGPoint {
    let x = positionForValue(value) - thumbImage.size.width / 2.0
    return CGPoint(x: x, y: (bounds.height - thumbImage.size.height) / 2.0)
  }
}

extension RangeSlider {
  override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
    // 1
    previousLocation = touch.location(in: self)
    
    // 2
    if lowerThumbImageView.frame.contains(previousLocation) {
      lowerThumbImageView.isHighlighted = true
    } else if upperThumbImageView.frame.contains(previousLocation) {
      upperThumbImageView.isHighlighted = true
    }
    
    // 3
    return lowerThumbImageView.isHighlighted || upperThumbImageView.isHighlighted
  }
  
  override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
    let location = touch.location(in: self)
    
    // 1
    let deltaLocation = location.x - previousLocation.x
    let deltaValue = (maximumValue - minimumValue) * deltaLocation / bounds.width
    
    previousLocation = location
    
    // 2
    if lowerThumbImageView.isHighlighted {
      lowerValue += deltaValue
      lowerValue = boundValue(lowerValue, toLowerValue: minimumValue,
                              upperValue: upperValue)
    } else if upperThumbImageView.isHighlighted {
      upperValue += deltaValue
      upperValue = boundValue(upperValue, toLowerValue: lowerValue,
                              upperValue: maximumValue)
    }
    
    sendActions(for: .valueChanged)
    return true
  }
  
  override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
    lowerThumbImageView.isHighlighted = false
    upperThumbImageView.isHighlighted = false
  }
  
  // 4
  private func boundValue(_ value: CGFloat, toLowerValue lowerValue: CGFloat, upperValue: CGFloat) -> CGFloat {
    return min(max(value, lowerValue), upperValue)
  }
}

后记

本篇主要讲述了自定义控件:可重复使用的滑块,感兴趣的给个赞或者关注~~~

相关文章

网友评论

      本文标题:UIKit框架(六) —— 自定义控件:可重复使用的滑块(二)

      本文链接:https://www.haomeiwen.com/subject/dtqafqtx.html