Перерисовывать CGContext после того, как он уже нарисован в Swift?

Я пытаюсь создать простое приложение с одним представлением для своей практики, но столкнулся с проблемой и не решал эту проблему последние пару дней.

Скриншот текущей программы

По сути, когда я нажимаю любую из этих стилевых кнопок или настраиваю ползунок, я хочу, чтобы он перерисовал 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")
}

и функция 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 selected") в конце, просто чтобы убедиться, что функция вызывается (и она вызывается), однако она не будет перерисовывать строку, что бы я ни пытался.

Любая помощь / совет будет высоко ценится.

2 ответа

Решение

Вы должны сделать весь свой рисунок внутри override func draw(_ rect: CGRect) (или внутри подпрограммы, вызываемые оттуда). Так что ваши draw нужен флаг, установленный снаружи (вашими действиями), чтобы указать, какой из ваших стилей рисовать. Вы запускаете перерисовку с setNeedsDisplay() - но это должно быть вызвано вашим действием, а не кодом рисования. Ваш текущий func drawStyleOne() пытается нарисовать (но, вероятно, неверный currentContext), а затем вызывает setNeedsDisplay(), который просто планирует ваш фактический draw рутина для запуска, которая всегда рисует один и тот же "стиль".

UIView класс работает за кулисами, чтобы сделать довольно много - если убедится, что draw не вызывается без необходимости, и планирует его, когда это необходимо, он определяет, какие части представления необходимо перерисовать, если они были скрыты (вот почему он передает вам rect) и устанавливает текущий графический контекст для draw, среди прочего.

Посмотрите этот учебник Рэя Вендерлиха или документы Apple. Как говорят в 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()
}

Я знаю, что это не совсем ответ на ваш вопрос, но вы можете посмотреть на свою проблему по-другому. Вам не нужно работать с контекстом или переопределять 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) 
   }
}

Тогда в вашем приложении делегат:

func styleButtonPressed() {
    NSLog("STYLE BUTTON PRESSED")
    _vectorView?.myColor = UIColor.green
    _vectorView?.myLength = [4,1]
    _vectorView?.setNeedsLayout()
}

Всякий раз, когда вы хотите изменить стиль, вы меняете переменные myPhase, myLength и myColor и вызываете yourView.setNeedsLayout() в приложении Delegate. Таким образом, вам просто нужен один метод для рисования всех ваших стилей.

Другие вопросы по тегам