UIDrawView使用drawRect绘制标尺 [英] UIScrollView draw ruler using drawRect

查看:77
本文介绍了UIDrawView使用drawRect绘制标尺的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在UIScrollView上绘制标尺.我这样做的方法是添加一个名为RulerView的自定义视图.我将此RulerView添加到scrollView的superview中,将其框架设置为与scrollView的框架相同.然后,我进行自定义绘制以在scrollView滚动时绘制线条.但是图形不平滑,滚动时结结巴巴,并且结束线或开始线突然出现/消失.我的drawRect有什么问题?

I am trying to draw a ruler on top of UIScrollView. The way I do it is by adding a custom view called RulerView. I add this rulerView to superview of scrollView setting its frame to be same as frame of scrollView. I then do custom drawing to draw lines as scrollView scrolls. But the drawing is not smooth, it stutters as I scroll and the end or begin line suddenly appears/disappears. What's wrong in my drawRect?

class RulerView: UIView {

   public var contentOffset = CGFloat(0) {
      didSet {
         self.setNeedsDisplay()
      }
   }

   public var contentSize = CGFloat(0)

   let smallLineHeight = CGFloat(4)
   let bigLineHeight = CGFloat(10)


  override open func layoutSubviews() {   
    super.layoutSubviews()
    self.backgroundColor = UIColor.clear     
  }

  override func draw(_ rect: CGRect) {
 
     UIColor.white.set()
     let contentWidth = max(rect.width, contentSize)
     let lineGap:CGFloat = 5
    
     let totalNumberOfLines = Int(contentWidth/lineGap)
    
     let startIndex = Int(contentOffset/lineGap)
     let endIndex = Int((contentOffset + rect.width)/lineGap)
     let beginOffset = contentOffset - CGFloat(startIndex)*lineGap
    
     if let context = UIGraphicsGetCurrentContext() {
        for i in startIndex...endIndex {
            let path = UIBezierPath()
            path.move(to: CGPoint(x: beginOffset + CGFloat(i - startIndex)*lineGap , y:0))
            path.addLine(to: CGPoint(x: beginOffset + CGFloat(i - startIndex)*lineGap, y: i % 5 == 0 ? bigLineHeight : smallLineHeight))
            path.lineWidth = 0.5
            path.stroke()

            
        }
    }
    
}

在scrollview委托中,我将其设置为:

And in the scrollview delegate, I set this:

  //MARK:- UIScrollViewDelegate

public func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let offset = scrollView.contentOffset.x
    
    rulerView.contentSize = scrollView.contentSize.width
    rulerView.contentOffset = offset
}

推荐答案

您的覆写功能绘制(_ rect:CGRect)非常繁重".我认为通过为刻度线"使用形状图层,您将获得更好的性能.并让 UIKit 处理图形.

Your override func draw(_ rect: CGRect) is very "heavy." I think you'll get much better performance by using a shape layer for your "tick marks" and letting UIKit handle the drawing.

编辑-根据评论

使用 CATextLayer 作为子层,在刻度线中添加了编号.

Added numbering to the tick marks using CATextLayer as sublayers.

这是一个示例 RulerView (使用您的刻度线尺寸和间距):

Here's a sample RulerView (using your tick mark dimensions and spacing):

class RulerView: UIView {

    public var contentOffset: CGFloat = 0 {
        didSet {
            layer.bounds.origin.x = contentOffset
        }
    }
    public var contentSize = CGFloat(0) {
        didSet {
            updateRuler()
        }
    }
    
    let smallLineHeight: CGFloat = 4
    let bigLineHeight: CGFloat = 10
    let lineGap:CGFloat = 5
    
    // numbers under the tick marks
    //  with 12-pt system font .light
    //  40-pt width will fit up to 5 digits
    let numbersWidth: CGFloat = 40
    let numbersFontSize: CGFloat = 12

    var shapeLayer: CAShapeLayer!
    
    override class var layerClass: AnyClass {
        return CAShapeLayer.self
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        shapeLayer = self.layer as? CAShapeLayer
        // these properties don't change
        backgroundColor = .clear
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.white.cgColor
        shapeLayer.lineWidth = 0.5
        shapeLayer.masksToBounds = true
    }
    func updateRuler() -> Void {
        // size is set by .fontSize, so ofSize here is ignored
        let numbersFont = UIFont.systemFont(ofSize: 1, weight: .light)
        let pth = UIBezierPath()
        var x: CGFloat = 0
        var i = 0
        while x < contentSize {
            pth.move(to: CGPoint(x: x, y: 0))
            pth.addLine(to: CGPoint(x: x, y: i % 5 == 0 ? bigLineHeight : smallLineHeight))
            
            // number every 10 ticks - change as desired
            if i % 10 == 0 {
                let layer = CATextLayer()
                
                layer.contentsScale = UIScreen.main.scale
                layer.font = numbersFont
                layer.fontSize = numbersFontSize
                layer.alignmentMode = .center
                layer.foregroundColor = UIColor.white.cgColor

                // if we want to number by tick count
                layer.string = "\(i)"
                
                // if we want to number by point count
                //layer.string = "\(i * Int(lineGap))"
                
                layer.frame = CGRect(x: x - (numbersWidth * 0.5), y: bigLineHeight, width: numbersWidth, height: numbersFontSize)
                
                shapeLayer.addSublayer(layer)
            }
            
            x += lineGap
            i += 1
        }
        shapeLayer.path = pth.cgPath

    }
}

这是一个示例控制器类,用于演示:

and here's a sample controller class to demonstrate:

class RulerViewController: UIViewController, UIScrollViewDelegate {
    var rulerView: RulerView = RulerView()
    var scrollView: UIScrollView = UIScrollView()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .blue
        
        [scrollView, rulerView].forEach {
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        // sample scroll content will be a horizontal stack view
        //  with 30 labels
        //  spaced 20-pts apart
        let stack = UIStackView()
        stack.translatesAutoresizingMaskIntoConstraints = false
        stack.spacing = 20
        
        for i in 1...30 {
            let v = UILabel()
            v.textAlignment = .center
            v.backgroundColor = .yellow
            v.text = "Label \(i)"
            stack.addArrangedSubview(v)
        }
        
        scrollView.addSubview(stack)
        
        let g = view.safeAreaLayoutGuide
        let contentG = scrollView.contentLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // scroll view 20-pts Top / Leading / Trailing
            scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            // scroll view Height: 60-pts
            scrollView.heightAnchor.constraint(equalToConstant: 60.0),
            
            // stack view 20-pts Top, 0-pts Leading / Trailing / Bottom (to scroll view's content layout guide)
            stack.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 20.0),
            stack.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 0.0),
            stack.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: 0.0),
            stack.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: 0.0),
            
            // ruler view 4-pts from scroll view Bottom
            rulerView.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 4.0),
            rulerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            // ruler view 0-pts from scroll view Leading / Trailing (equal width and horizontal position of scroll view)
            rulerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            // ruler view Height: 24-pts (make sure it's enough to accomodate ruler view's bigLineHeight plus numbering height)
            rulerView.heightAnchor.constraint(equalToConstant: 24.0),

        ])
        
        scrollView.delegate = self
        
        // so we can see the sroll view frame
        scrollView.backgroundColor = .red
        
        // if we want to see the rulerView's frame
        //rulerView.backgroundColor = .brown
        
    }
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // this is when we know the scroll view's content size
        rulerView.contentSize = scrollView.contentSize.width
    }
    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // update rulerView's x-offset
        rulerView.contentOffset = scrollView.contentOffset.x
    }
    
}

输出:

当然,刻度线(和数字)将与滚动视图左右同步滚动.

the tick marks (and numbers) will, of course, scroll left-right synched with the scroll view.

这篇关于UIDrawView使用drawRect绘制标尺的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆