ios - UIScrollView 使用 drawRect 绘制标尺
问题描述
我正在尝试在 UIScrollView 上绘制标尺。我这样做的方法是添加一个名为 RulerView 的自定义视图。我将此标尺视图添加到scrollView 的superview 中,将其框架设置为与scrollView 的框架相同。然后我进行自定义绘图以在 scrollView 滚动时绘制线条。但是绘图并不流畅,当我滚动时它会结结巴巴,结束线或开始线突然出现/消失。我的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()
}
}
}
在滚动视图委托中,我设置了这个:
//MARK:- UIScrollViewDelegate
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.x
rulerView.contentSize = scrollView.contentSize.width
rulerView.contentOffset = offset
}
解决方案
你override func draw(_ rect: CGRect)
的很“重”。UIKit
我认为通过使用形状图层作为“刻度线”并让处理绘图,您将获得更好的性能。
编辑-根据评论
CATextLayer
使用作为子层为刻度线添加编号。
这是一个示例RulerView
(使用您的刻度线尺寸和间距):
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
}
}
这是一个示例控制器类来演示:
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
}
}
输出:
刻度线(和数字)当然会与滚动视图同步左右滚动。
推荐阅读
- javascript - 数据表服务器端Ajax分页
- r - ggplot2 - 严格限制 x 和 y 轴
- ruby-on-rails - 使用 exceptions_app 时未清除 Flash
- html - 如何不重叠表和行?
- azure - Azure 函数队列触发器的 Azure 存储模拟器连接字符串
- html - 如何将子列表项(如示例图像)居中?
- vb.net - 从文本框中添加数组值并显示在标签中
- android - 使用 java 代码为 android API 6 和更少的启用-禁用 Wifi 热点和网络共享
- php - Laravel eloquent 属于在列中以逗号分隔的多个 id
- android - 如何阅读 OpenSSL 源代码