27
  1. If I draw my chart inside - (void)drawRect:(CGRect)rect is just enough to set [_chartView setContentMode:UIViewContentModeRedraw] and this method will be called when device changes it's orienatation and it's possible to calculate f.e. new center point for my chart.
  2. If I create a view like a subview using - (id)initWithFrame:(CGRect)frame and then add it in view controller like [self.view addSubview:chartView];. How in this case I can handle rotation to redraw my chart?
pvllnspk
  • 5,667
  • 12
  • 59
  • 97
  • @Keenle,@Kijug when you draw using the second way you do not use drawRect method, see f.e https://github.com/kevinzhow/PNChart/blob/master/PNChart/PNPieChart/PNPieChart.m – pvllnspk Jul 11 '14 at 07:56
  • + needs to call redraw method only first time when device orientation changes like drawRect does – pvllnspk Jul 11 '14 at 08:02
  • I've updated the answer. Had to create a test project with chart control to figure out the problem. BTW the charts component is simple and great! – Keenle Jul 11 '14 at 13:24

8 Answers8

42

While a preferred solution requires zero lines of code, if you must trigger a redraw, do so in setNeedsDisplay, which in turn invokes drawRect.
No need to listen to notifications nor refactor the code.

Swift

override func layoutSubviews() {
    super.layoutSubviews()
    self.setNeedsDisplay()
}

Objective-C

- (void)layoutSubviews {
    [super layoutSubviews];
    [self setNeedsDisplay];
}

Note:
layoutSubviews is a UIView method, not a UIViewController method.

SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179
  • This seems like a good solution, but when I put this into my TableViewController it is not called upon rotation. Perhaps it's my target version of iOS (8.1) or something, but it doesn't work. – Damien Del Russo Dec 30 '15 at 02:54
  • 1
    `layoutSubviews` is a `UIView` method, not a `UIViewController` method. You need to make a subclass of `UITableView`, not `UITableViewController`, which can be done in **IB**. You may want to read more about layoutSubviews on http://stackoverflow.com/a/5330162/218152. – SwiftArchitect Dec 30 '15 at 05:04
  • Cool, thanks. My views were actually redrawing already from setting contentMode = .Redraw on the TableView in IB. The problem I had was my own fault, since corrected. Thanks! – Damien Del Russo Dec 31 '15 at 18:10
  • This does not work in my testing. Orientation change does not trigger a redraw. And I am using this on a class I created that subclasses UIView – johnny Jul 26 '19 at 20:08
15

To make your chart rendered correctly when device orientation changes you need to update chart's layout, here is the code that you should add to your view controller:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    _chartView.frame = self.view.bounds;
    [_chartView strokeChart];
}
Keenle
  • 12,010
  • 3
  • 37
  • 46
8

Zero Lines of Code

Use .redraw

Programmatically invoking myView.contentMode = .redraw when creating the custom view should suffice. It is a single flag in IB and, as such, the 0 lines of code prefered way. See Stack Overflow How to trigger drawRect on UIView subclass.

Community
  • 1
  • 1
SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179
4

Go here to learn how to receive notifications for when the device orientation changes. When the orientation does change, just call [chartView setNeedsDisplay]; to make drawRect: get called so you can update your view. Hope this helps!

Community
  • 1
  • 1
Adam Evans
  • 2,072
  • 1
  • 20
  • 29
2

The code you'll add to your view controller:

- (void)updateViewConstraints
{
    [super updateViewConstraints];
    [_chartView setNeedsDisplay];
}
2cupsOfTech
  • 5,953
  • 4
  • 34
  • 57
1

Unfortunately some answers suggest to override controller methods, but I've some custom UITableViewCells with a shadow around and rotating the device stretches the cells but doesn't redraw the shadow. So I don't want to put my code within a controller to (re)draw a subview. The solution for me is to listen to a UIDeviceOrientationDidChange notification within my custom UITableViewCell and then call setNeedsDisplay() as suggested.

Swift 4.0 Code example in one of my custom UITableViewCells

override func awakeFromNib() {
    super.awakeFromNib()

    NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChangeNotification), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}

@objc func deviceOrientationDidChangeNotification(_ notification: Any) {
        Logger.info("Orientation changed: \(notification)")
        setNeedsLayout()
}

BTW: Logger is a typealias to SwiftyBeaver. Thanks to @Aderis pointing me to the right direction.

Sal
  • 1,230
  • 13
  • 21
0

Thanks Keenle, for the above ObjC solution. I was messing around with creating programmed constraints all morning, killing my brain, not understanding why they would not recompile/realign the view. I guess because I had created the UIView programmatically using CGRect...this was taking precedence.

However, here was my solution in swift:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    myUIView.center = CGPoint(x: (UIScreen.mainScreen().bounds.width / 2), y: (UIScreen.mainScreen().bounds.height / 2))
}

So relieved! :)

koen
  • 5,383
  • 7
  • 50
  • 89
David West
  • 1,550
  • 1
  • 18
  • 31
0

I tried "setNeedsDisplay" in a dozen ways and it didn't work for adjusting the shadow of a view I was animating to a new position.

I did solve my problem with this though. If your shadow doesn't seem to want to cooperate/update, you could try just setting the shadow to nil, then setting it again:

    UIView.animateKeyframes(withDuration: 0.2 /*Total*/, delay: 0.0, options: UIViewKeyframeAnimationOptions(), animations: {
        UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 10/10, animations:{
            // Other things to animate

            //Set shadow to nil
            self.viewWithShadow.layer.shadowPath = nil
        })
    }, completion: { finished in
        if (!finished) { return }
        // When the view is moved, set the shadow again
        self.viewWithShadow.layer.shadowPath = UIBezierPath(rect: self.descTextView.bounds).cgPath
    })

If you don't even have a shadow yet and need that code, here's that:

func addShadowTo (view: UIView) {
    view.layer.masksToBounds = false
    view.layer.shadowColor = UIColor.gray.cgColor
    view.layer.shadowOffset = CGSize( width: 1.0, height: 1.0)
    view.layer.shadowOpacity = 0.5
    view.layer.shadowRadius = 6.0
    view.layer.shadowPath = UIBezierPath(rect: view.bounds).cgPath
    view.layer.shouldRasterize = true
}
Dave G
  • 12,042
  • 7
  • 57
  • 83