【问题标题】:Redrawing CGContext after it's already been drawn in Swift?在 Swift 中绘制后重绘 CGContext?
【发布时间】:2017-02-02 07:39:42
【问题描述】:

我正在尝试为自己的练习制作一个简单的单视图应用程序,但我遇到了一个问题,并且在过去的几天里一直没有解决这个问题。

Screenshot of the current program

基本上,当我按下任何这些样式按钮或调整滑块时,我希望它重新绘制屏幕底部的 CGContext(红色虚线)。例如,如果我按下 STYLE 1 按钮,它应该调用名为 drawStyleOne() 的函数并应用这两个不同的更改:

context?.setLineDash(phase: 0, lengths: [2,3])
context?.setStrokeColor(UIColor.green.cgColor)

我的目标是重新绘制虚线(较小的点)并将上下文的颜色更改为绿色。 (上下文是CGContext的一个实例)

我已经在覆盖函数 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")
}

上面类中的 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") 放在最后只是为了验证该函数是否被调用(确实如此),但是无论我尝试什么,它都不会重绘线。

任何帮助/建议将不胜感激。

【问题讨论】:

    标签: ios swift uikit cgcontext redraw


    【解决方案1】:

    您需要在override func draw(_ rect: CGRect)(或从那里调用的例程)内完成所有您的绘图。因此,您的 draw 需要在外部设置一个标志(通过您的操作)来告诉它您要绘制哪种样式。您使用setNeedsDisplay() 触发重绘 - 但这应该从您的操作中调用,而不是从绘图代码中调用。您当前的 func drawStyleOne() 尝试 绘制(但可能是无效的 currentContext)然后调用 setNeedsDisplay(),它只是安排您实际的 draw 例程运行,它总是绘制相同的“样式”。

    UIView 类在幕后做了很多工作——如果确保draw 不会被不必要地调用,并在必要时安排它,它会确定视图的哪些部分需要重新如果它们被遮挡,则绘制它们(这就是它传递给您rect 的原因),并为draw 设置当前图形上下文等。

    请参阅此Ray Wenderlich 教程或Apple docs。正如 Apple 文档所说:

    对于自定义视图,您必须重写 drawRect: 方法并执行 所有你的画在里面。

    (我的重点)

    所以你的代码应该是这样的:

    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()
    }
    

    【讨论】:

    • 非常感谢您的回复。我非常想对此投赞成票,但我现在没有足够的声望点...... :(
    • @whgnsdlr7 - 你应该仍然能够接受它作为正确答案(这本身可能会给你足够的代表来投票!):)
    【解决方案2】:

    我知道这并不是您问题的真正答案,但您可以换一种方式看待您的问题。您实际上不需要使用上下文或覆盖 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()。这样,您只需要一种方法来绘制所有样式。

    【讨论】:

    • 我认为这也可以正常工作。我也得试试这个。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多