UIDrawView使用drawRect绘制标尺 [英] UIScrollView draw ruler using 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屋!