redraw a custom uiview when changing a device orientation
Asked Answered
H

8

29
  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?
Histrionic answered 10/7, 2014 at 18:31 Comment(3)
@Keenle,@Kijug when you draw using the second way you do not use drawRect method, see f.e github.com/kevinzhow/PNChart/blob/master/PNChart/PNPieChart/…Histrionic
+ needs to call redraw method only first time when device orientation changes like drawRect doesHistrionic
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!Matty
M
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];
}
Matty answered 10/7, 2014 at 23:15 Comment(1)
I believe its highly discouraged to set frame in code since auto-layouts came out, check my answerHedy
W
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.

Wifely answered 2/10, 2015 at 7:21 Comment(4)
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.Broncho
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 https://mcmap.net/q/56540/-when-is-layoutsubviews-called.Wifely
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!Broncho
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 UIViewBatts
M
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];
}
Matty answered 10/7, 2014 at 23:15 Comment(1)
I believe its highly discouraged to set frame in code since auto-layouts came out, check my answerHedy
W
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.

Wifely answered 26/11, 2019 at 3:33 Comment(1)
Great answer, elegant and does the job, +1.Praefect
S
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!

Staciestack answered 10/7, 2014 at 22:27 Comment(1)
This is the right answer, the UIView subclass shall handle this by itself.Radiology
H
2

The code you'll add to your view controller:

- (void)updateViewConstraints
{
    [super updateViewConstraints];
    [_chartView setNeedsDisplay];
}
Hedy answered 23/2, 2015 at 17:12 Comment(3)
I tried this, but it isn't called upon device rotation.Broncho
@DamienDelRusso there might be some other reason why its not being called for you on device rotation, look aroundHedy
I would look, but I found out my views are redrawing with contentMode set to .Redraw. The reason it wasn't working for me was my own dumb error, since fixed. Thanks!Broncho
D
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.

Dianetics answered 2/1, 2018 at 12:5 Comment(0)
T
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! :)

Tybalt answered 9/1, 2016 at 13:48 Comment(0)
B
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
}
Brogue answered 8/6, 2018 at 4:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.