4

I want to rotate a UIView with a drawing inside (circle, rectangle or line). The problem is when I rotate the view and refresh the drawing (I need ir when I change some properties of the drawing, i.e) the drawing doesn't follow the view...

UIView without rotation:

UIView without rotation

UIView with rotation:

UIView rotated and drawing distortion

Here is my simple code (little extract from the original code):

Main ViewController

class TestRotation: UIViewController {

// MARK: - Properties

var drawingView:DrawingView?

// MARK: - Actions

@IBAction func actionSliderRotation(_ sender: UISlider) {

    let degrees = sender.value
    let radians = CGFloat(degrees * Float.pi / 180)
    drawingView?.transform = CGAffineTransform(rotationAngle: radians)

    drawingView?.setNeedsDisplay()
}

// MARK: - Init

override func viewDidLoad() {
    super.viewDidLoad()

    drawingView = DrawingView(frame:CGRect(x:50, y:50, width: 100, height:75))

    self.view.addSubview(drawingView!)

    drawingView?.setNeedsDisplay() 
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

}

UIView

class DrawingView: UIView {

override func draw(_ rect: CGRect) {

    let start = CGPoint(x: 0, y: 0)
    let end = CGPoint(x: self.frame.size.width, y: self.frame.size.height)

    drawCicrle(start: start, end: end)
}

func drawCicrle(start:CGPoint, end:CGPoint) {

    //Contexto
    let context = UIGraphicsGetCurrentContext()

    if context != nil {

        //Ancho
        context!.setLineWidth(2)

        //Color
        context!.setStrokeColor(UIColor.black.cgColor)
        context!.setFillColor(UIColor.orange.cgColor)

        let rect = CGRect(origin: start, size: CGSize(width: end.x-start.x, height: end.y-start.y))

        let path = UIBezierPath(ovalIn: rect)

        path.lineWidth = 2
        path.fill()
        path.stroke()

    }
}

// MARK: - Init

override init(frame: CGRect) {
    super.init(frame: frame)

    self.backgroundColor = UIColor.gray
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

}

It seems that the problem is temporally solved if I don't refresh the view after rotating, but if I have to refresh later for some other reason (properties changed), the problem appears again.

What can I do? Thanks in advance

vacawama
  • 150,663
  • 30
  • 266
  • 294

1 Answers1

4

You are using the frame when you should be using the bounds.

The frame is the rectangle which contains the view. It is specified in the coordinate system of the superview of the view, and the frame changes when you rotate the view (because a rotated square extends further in the X and Y directions than a non-rotated square).

The bounds is the rectangle which contains the contents of the view. It is specified in the coordinate system of the view itself, and it doesn't change as the view is rotated or moved.

If you change frame to bounds in your draw(_:) function, it will work as you expect:

override func draw(_ rect: CGRect) {

    let start = CGPoint(x: 0, y: 0)
    let end = CGPoint(x: self.bounds.size.width, y: self.bounds.size.height)

    drawCicrle(start: start, end: end)
}

Here's a demo showing the oval being redrawn as the slider moves.

Rotate Oval demo


Here's the changed code:

class DrawingView: UIView {

    var fillColor = UIColor.orange {
        didSet {
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {

        let start = CGPoint(x: 0, y: 0)
        let end = CGPoint(x: self.bounds.size.width, y: self.bounds.size.height)

        drawCircle(start: start, end: end)
    }

    func drawCircle(start:CGPoint, end:CGPoint) {

        //Contexto
        let context = UIGraphicsGetCurrentContext()

        if context != nil {

            //Ancho
            context!.setLineWidth(2)

            //Color
            context!.setStrokeColor(UIColor.black.cgColor)
            context!.setFillColor(fillColor.cgColor)

            let rect = CGRect(origin: start, size: CGSize(width: end.x-start.x, height: end.y-start.y))

            let path = UIBezierPath(ovalIn: rect)

            path.lineWidth = 2
            path.fill()
            path.stroke()

        }
    }

    // MARK: - Init

    override init(frame: CGRect) {
        super.init(frame: frame)

        self.backgroundColor = UIColor.gray
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

}

@IBAction func actionSliderRotation(_ sender: UISlider) {

    let degrees = sender.value
    let radians = CGFloat(degrees * Float.pi / 180)
    drawingView?.transform = CGAffineTransform(rotationAngle: radians)

    drawingView?.fillColor = UIColor(hue: CGFloat(degrees)/360.0, saturation: 1.0, brightness: 1.0, alpha: 1.0)

}
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • Thanks! I have seen your answer after fighting a lot, and before I saw it I discover the issue about rect vs bounds... ;) – Jaime Bocio Jan 22 '17 at 16:16
  • I assumed that the frame height/width was equal to the bounds height/width but this appears to be wrong when rotating. To everyone reading: make sure you replace all your code using `frame` to `bounds` instead! – Carl May 17 '19 at 13:40