在 Swift 中绘制后重绘 CGContext?
Redrawing CGContext after it's already been drawn in Swift?
我正在尝试为自己的实践制作一个简单的单视图应用程序,但我 运行 遇到了一个问题,并且在过去几天都没有解决这个问题。
Screenshot of the current program
基本上,当我按下任何这些样式按钮或调整滑块时,我希望它重新绘制屏幕底部的 CGContext(红色虚线)。例如,如果我按下 STYLE 1 按钮,它应该调用名为 drawStyleOne() 的函数并应用这两个不同的更改:
context?.setLineDash(phase: 0, lengths: [2,3])
context?.setStrokeColor(UIColor.green.cgColor)
我的目标是重新绘制虚线(较小的点)并将上下文的颜色更改为绿色。 (上下文是 CGContext 的一个实例)
我在覆盖 func draw(_ rect: CGRect) 上绘制了所有内容,但我无法让它按照我想要的方式重新绘制 CGContext。
这是我的部分代码,有助于更好地解释这种情况。
import UIKit
class VectorView: UIView {
private var context: CGContext? = nil
override init(frame: CGRect) {
super.init(frame: frame)
//contentMode = .redraw //tried this, didn't work.
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
// lets us make calls
context = UIGraphicsGetCurrentContext()!
context?.move(to: CGPoint(x: 0.0, y: 10.0))
context?.setLineDash(phase: 0, lengths: [2,3])
context?.setLineJoin(CGLineJoin.bevel)
context?.setLineCap(CGLineCap.square)
context?.addLine(to: CGPoint(x: 50.0, y: 70.0))
context?.addLine(to: CGPoint(x: 100.0, y: 20.0))
context?.setLineWidth(1.0)
context?.setStrokeColor(UIColor.red.cgColor)
context?.drawPath(using: CGPathDrawingMode.stroke)
}
func drawStyleOne () {
context = UIGraphicsGetCurrentContext()
context?.move(to: CGPoint(x: 0.0, y: 10.0))
context?.setLineDash(phase: 0, lengths: [2,3])
context?.setLineJoin(CGLineJoin.bevel)
context?.setLineCap(CGLineCap.square)
context?.addLine(to: CGPoint(x: 50.0, y: 70.0))
context?.addLine(to: CGPoint(x: 100.0, y: 20.0))
context?.setStrokeColor(UIColor.green.cgColor)
context?.drawPath(using: CGPathDrawingMode.stroke)
self.setNeedsDisplay()
//contentMode = .redraw //Tried this and didn't work either..
NSLog("drawStyleOne got called")
}
上面 class 中的 drawStyleOne 函数在 AppDelegate 中被调用。
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let screenSize = UIScreen.main.bounds
private var _vectorView: VectorView? = nil
private var _buttonStylesView: ButtonStyleView? = nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow()
window?.rootViewController = ViewController()
window?.rootViewController?.view.backgroundColor = UIColor.white
window?.makeKeyAndVisible()
_vectorView = VectorView()
_vectorView?.frame = CGRect(x: 0.0, y: 650, width: 414, height: 70)
_vectorView?.backgroundColor = UIColor.lightGray
window?.rootViewController?.view.addSubview(_vectorView!)
_buttonStylesView = ButtonStyleView()
_buttonStylesView?.frame = CGRect (x: screenSize.width / 10, y: screenSize.height * 6 / 10, width: 414, height: 100)
_buttonStylesView?.backgroundColor = UIColor.clear
window?.rootViewController?.view.addSubview(_buttonStylesView!)
_buttonStylesView?.styleOne?.addTarget(self, action: #selector(styleButtonPressed), for: UIControlEvents.touchDown)
return true
}
func styleButtonPressed() {
NSLog("STYLE BUTTON PRESSED")
_vectorView?.drawStyleOne()
}
}
我什至将 NSLog("drawStyleOne got called") 放在末尾只是为了验证该函数是否被调用(确实如此),但是无论我尝试什么它都不会重绘该行。
任何 help/advice 将不胜感激。
您需要在 override func draw(_ rect: CGRect)
(或从那里调用的例程)中完成 所有 绘图。因此,您的 draw
需要在外部设置一个标志(通过您的操作)来告诉它要绘制哪种样式。您使用 setNeedsDisplay()
触发重绘 - 但应该从您的操作中调用,而不是从绘图代码中调用。您当前的 func drawStyleOne()
尝试 绘制(但可能是无效的 currentContext)然后调用 setNeedsDisplay()
,这只是安排您实际的 draw
例程运行,它总是绘制相同的 "style"。
UIView
class 在幕后工作做很多事情 - 如果确保 draw
不会被不必要地调用,并在必要时安排它,它找出视图的哪些部分需要 re-drawn 如果它们被遮挡(这就是为什么它传递给你一个 rect
),并为 draw
设置当前图形上下文,等等.
看到这个 Ray Wenderlich tutorial, or the Apple docs。正如 Apple 文档所说:
For custom views, you must override the drawRect: method and perform
all your drawing inside it.
(我的重点)
因此您的代码应如下所示:
class VectorView: UIView {
public var whichStyle: SomeEnumOfYours = // your default value
override func draw(_ rect: CGRect) {
switch self.whichStyle {
case .style1:
self.drawStyleOne()
case .style2:
self.drawStyleTwo()
//...
}
}
}
//...
func styleButtonPressed() {
NSLog("STYLE BUTTON PRESSED")
_vectorView?.whichStyle = // whichever style required
_vectorView?.setNeedsDisplay()
}
我知道这并不是您问题的真正答案,但您可以换个角度看待您的问题。您实际上不需要使用上下文或覆盖 drawRect 来绘制线条。您可以直接使用图层。将它们添加到您的视图中,并在您想要更改样式并添加另一个样式时删除它们。你会像这样使用它:
import UIKit
class VectorView: UIView {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var myColor = UIColor.red
var myPhase = 0
var myLength = [2,3]
override func layoutSubviews() {
super.layoutSubviews()
guard let sublayers = self.layer.sublayers else { return }
// remove sublayer if any exists
for layer in sublayers {
layer.removeFromSuperlayer()
}
// draws your lines according to the style you have defined with your variables above
styledDrawing()
}
func styledDrawing() {
// creates a path and styles it
let path = UIBezierPath()
path.move(to: CGPoint(x: 0.0, y: 10.0))
path.setLineDash(myLength, count: myLength.count, phase: myPhase)
path.lineJoinStyle = .bevel
path.lineCapStyle = .square
// add lines to the path
path.addLine(to: CGPoint(x: 50.0, y: 70.0))
path.addLine(to: CGPoint(x: 100.0, y: 20.0))
// creates a layer,adds your path to it and adds the layer to your view's layer
let layer = CAShapeLayer()
layer.path = path.cgPath
layer.lineWidth = 1.0
layer.strokeColor = myColor.cgColor
self.layer.addSublayer(layer)
}
}
然后在你的appDelegate中:
func styleButtonPressed() {
NSLog("STYLE BUTTON PRESSED")
_vectorView?.myColor = UIColor.green
_vectorView?.myLength = [4,1]
_vectorView?.setNeedsLayout()
}
每当您想更改样式时,您都可以更改变量 myPhase、myLength 和 myColor 并在 appDelegate 中调用 yourView.setNeedsLayout()
。这样你只需要一种方法来绘制你所有的样式。
我正在尝试为自己的实践制作一个简单的单视图应用程序,但我 运行 遇到了一个问题,并且在过去几天都没有解决这个问题。
Screenshot of the current program
基本上,当我按下任何这些样式按钮或调整滑块时,我希望它重新绘制屏幕底部的 CGContext(红色虚线)。例如,如果我按下 STYLE 1 按钮,它应该调用名为 drawStyleOne() 的函数并应用这两个不同的更改:
context?.setLineDash(phase: 0, lengths: [2,3])
context?.setStrokeColor(UIColor.green.cgColor)
我的目标是重新绘制虚线(较小的点)并将上下文的颜色更改为绿色。 (上下文是 CGContext 的一个实例)
我在覆盖 func draw(_ rect: CGRect) 上绘制了所有内容,但我无法让它按照我想要的方式重新绘制 CGContext。
这是我的部分代码,有助于更好地解释这种情况。
import UIKit
class VectorView: UIView {
private var context: CGContext? = nil
override init(frame: CGRect) {
super.init(frame: frame)
//contentMode = .redraw //tried this, didn't work.
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
// lets us make calls
context = UIGraphicsGetCurrentContext()!
context?.move(to: CGPoint(x: 0.0, y: 10.0))
context?.setLineDash(phase: 0, lengths: [2,3])
context?.setLineJoin(CGLineJoin.bevel)
context?.setLineCap(CGLineCap.square)
context?.addLine(to: CGPoint(x: 50.0, y: 70.0))
context?.addLine(to: CGPoint(x: 100.0, y: 20.0))
context?.setLineWidth(1.0)
context?.setStrokeColor(UIColor.red.cgColor)
context?.drawPath(using: CGPathDrawingMode.stroke)
}
func drawStyleOne () {
context = UIGraphicsGetCurrentContext()
context?.move(to: CGPoint(x: 0.0, y: 10.0))
context?.setLineDash(phase: 0, lengths: [2,3])
context?.setLineJoin(CGLineJoin.bevel)
context?.setLineCap(CGLineCap.square)
context?.addLine(to: CGPoint(x: 50.0, y: 70.0))
context?.addLine(to: CGPoint(x: 100.0, y: 20.0))
context?.setStrokeColor(UIColor.green.cgColor)
context?.drawPath(using: CGPathDrawingMode.stroke)
self.setNeedsDisplay()
//contentMode = .redraw //Tried this and didn't work either..
NSLog("drawStyleOne got called")
}
上面 class 中的 drawStyleOne 函数在 AppDelegate 中被调用。
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let screenSize = UIScreen.main.bounds
private var _vectorView: VectorView? = nil
private var _buttonStylesView: ButtonStyleView? = nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow()
window?.rootViewController = ViewController()
window?.rootViewController?.view.backgroundColor = UIColor.white
window?.makeKeyAndVisible()
_vectorView = VectorView()
_vectorView?.frame = CGRect(x: 0.0, y: 650, width: 414, height: 70)
_vectorView?.backgroundColor = UIColor.lightGray
window?.rootViewController?.view.addSubview(_vectorView!)
_buttonStylesView = ButtonStyleView()
_buttonStylesView?.frame = CGRect (x: screenSize.width / 10, y: screenSize.height * 6 / 10, width: 414, height: 100)
_buttonStylesView?.backgroundColor = UIColor.clear
window?.rootViewController?.view.addSubview(_buttonStylesView!)
_buttonStylesView?.styleOne?.addTarget(self, action: #selector(styleButtonPressed), for: UIControlEvents.touchDown)
return true
}
func styleButtonPressed() {
NSLog("STYLE BUTTON PRESSED")
_vectorView?.drawStyleOne()
}
}
我什至将 NSLog("drawStyleOne got called") 放在末尾只是为了验证该函数是否被调用(确实如此),但是无论我尝试什么它都不会重绘该行。
任何 help/advice 将不胜感激。
您需要在 override func draw(_ rect: CGRect)
(或从那里调用的例程)中完成 所有 绘图。因此,您的 draw
需要在外部设置一个标志(通过您的操作)来告诉它要绘制哪种样式。您使用 setNeedsDisplay()
触发重绘 - 但应该从您的操作中调用,而不是从绘图代码中调用。您当前的 func drawStyleOne()
尝试 绘制(但可能是无效的 currentContext)然后调用 setNeedsDisplay()
,这只是安排您实际的 draw
例程运行,它总是绘制相同的 "style"。
UIView
class 在幕后工作做很多事情 - 如果确保 draw
不会被不必要地调用,并在必要时安排它,它找出视图的哪些部分需要 re-drawn 如果它们被遮挡(这就是为什么它传递给你一个 rect
),并为 draw
设置当前图形上下文,等等.
看到这个 Ray Wenderlich tutorial, or the Apple docs。正如 Apple 文档所说:
For custom views, you must override the drawRect: method and perform all your drawing inside it.
(我的重点)
因此您的代码应如下所示:
class VectorView: UIView {
public var whichStyle: SomeEnumOfYours = // your default value
override func draw(_ rect: CGRect) {
switch self.whichStyle {
case .style1:
self.drawStyleOne()
case .style2:
self.drawStyleTwo()
//...
}
}
}
//...
func styleButtonPressed() {
NSLog("STYLE BUTTON PRESSED")
_vectorView?.whichStyle = // whichever style required
_vectorView?.setNeedsDisplay()
}
我知道这并不是您问题的真正答案,但您可以换个角度看待您的问题。您实际上不需要使用上下文或覆盖 drawRect 来绘制线条。您可以直接使用图层。将它们添加到您的视图中,并在您想要更改样式并添加另一个样式时删除它们。你会像这样使用它:
import UIKit
class VectorView: UIView {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var myColor = UIColor.red
var myPhase = 0
var myLength = [2,3]
override func layoutSubviews() {
super.layoutSubviews()
guard let sublayers = self.layer.sublayers else { return }
// remove sublayer if any exists
for layer in sublayers {
layer.removeFromSuperlayer()
}
// draws your lines according to the style you have defined with your variables above
styledDrawing()
}
func styledDrawing() {
// creates a path and styles it
let path = UIBezierPath()
path.move(to: CGPoint(x: 0.0, y: 10.0))
path.setLineDash(myLength, count: myLength.count, phase: myPhase)
path.lineJoinStyle = .bevel
path.lineCapStyle = .square
// add lines to the path
path.addLine(to: CGPoint(x: 50.0, y: 70.0))
path.addLine(to: CGPoint(x: 100.0, y: 20.0))
// creates a layer,adds your path to it and adds the layer to your view's layer
let layer = CAShapeLayer()
layer.path = path.cgPath
layer.lineWidth = 1.0
layer.strokeColor = myColor.cgColor
self.layer.addSublayer(layer)
}
}
然后在你的appDelegate中:
func styleButtonPressed() {
NSLog("STYLE BUTTON PRESSED")
_vectorView?.myColor = UIColor.green
_vectorView?.myLength = [4,1]
_vectorView?.setNeedsLayout()
}
每当您想更改样式时,您都可以更改变量 myPhase、myLength 和 myColor 并在 appDelegate 中调用 yourView.setNeedsLayout()
。这样你只需要一种方法来绘制你所有的样式。