Custom UIView's drawRect never called if initial frame is CGRectZero - ios

I've got a pretty simple custom subclass of UIView:
#import "BarView.h"
#import <QuartzCore/QuartzCore.h>
#implementation BarView
#synthesize barColor;
- (void)drawRect:(CGRect)rect
{
NSLog(#"drawRect");
// Draw a rectangle.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, self.barColor.CGColor);
CGContextBeginPath(context);
CGContextAddRect(context, self.bounds);
CGContextFillPath(context);
}
- (void)dealloc
{
self.barColor = nil;
[super dealloc];
}
#end
If I call initWithFrame:rect on this class with some non-zero rect, it works fine; but when I call initWithFrame:CGRectZero, drawRect is -never- called, even after I modify the view's frame to a non-zero rect.
I certainly understand why a view with a zero frame would never have its drawRect: called, but why is it never called even after the frame is changed?

A quick look at the Apple docs says this
Changing the frame rectangle automatically redisplays the view without calling its drawRect: method. If you want UIKit to call the drawRect: method when the frame rectangle changes, set the contentMode property to UIViewContentModeRedraw.
This is in the documentation for the frame property:
https://developer.apple.com/documentation/uikit/uiview/1622621-frame?language=objc
If you want the view to redraw, just call setNeedsDisplay on it and you should be fine (or, as the docs say, you can set it to UIViewContentModeRedraw, but it is up to you)

In Swift 5
override init(frame: CGRect) {
super.init(frame: frame)
contentMode = UIView.ContentMode.redraw

Related

When should I be setting borders in UIViews?

I've got a UIView that does not fill the whole screen and I would like to add a top border to that view. However, I keep getting the following:
Here is the code I am using:
CGFloat thickness = 4.0f;
CALayer *topBorder = [CALayer layer];
topBorder.frame = CGRectMake(0, 0, self.announcementCard.frame.size.width, thickness);
topBorder.backgroundColor = [UIColor blueColor].CGColor;
How I know why the border goes off the screen. This is because I put the border on the view inside the UIViews init method. When I do this the self.announcementCard.frame.size.width is 1000 and hence why the border goes off the screen. The self.announcementCard.frame.size.width has a width and height of 1000. The reason for this is because the UIView hasn't added the constraints to the UIView in its init methods.
Thus, my question is when should I be calling the code I've written above? When will self.announcementCard.frame.size.width have its constraints added to it and have its frame updated?
You should add your subviews (or sublayers) in the viewDidLoad method. However if you are using the auto-layout keep a reference of your sublayer and update it in the viewDidLayoutSubviews method:
- (void)viewDidLoad {
[super viewDidLoad];
_borderLayer = [CALayer layer];
[self.view.layer addSublayer:_borderLayer];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
_borderLayer.frame = CGRectMake(0, 0, self.view.bounds.size.width, 3);
}
Otherwise you can simply clipsToBounds the view to avoid the subviews to be visible beyond the bounds.
self.view.clipsToBounds = YES;
in the init the graphics isn't made yet. You have to put all your configuration on graphics object in the viewDidLoad: or viewWillAppear: of the UIViewController.
Short Answer:
viewWillAppear
By the time viewWillAppear is called, your subviews have been laid out and the frames are valid. Doing frame-based calculations in viewDidLoad can often have issues since the frames have not been set.

Custom UIView Subclass's drawRect not being called inside UITableViewCell

Background:
I have a subclass of UITableViewCell whose layout is defined as a prototype cell in a storyboard. One of the cell's IBOutlets is a subclass of UIView I made called BorderedProfileImageView. This class's drawRect: function is overridden as follows:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [[UIColor blueColor] CGColor]);
CGContextSetLineWidth(context, 1.f);
CGFloat halfWidth = self.bounds.size.width/2;
NSInteger radius = sqrt(pow(halfWidth, 2) + pow(halfWidth, 2)) - 8.5;
CGContextAddArc(context,self.bounds.size.width/2,self.bounds.size.height/2,radius,0,2*3.1415926535898,1);
CGContextStrokePath(context);
}
This adds a blue ring to the UIView.
Problem:
The blue ring never appears, and BorderdProfileImageView's drawRect: function never gets called. The cell still draws, the UIImageView which is inside the BorderdProfileImageView gets drawn. I do call setNeedsDisplay on the BorderedProfileImageView when setting up the cell.
Why is drawRect: not getting called?
I figured it out.
The object in IB which was set to the BorderedProfileImageView was originally a UIImageView. Changing this object to a UIView and re-linking has resulted in the desired behavior.

IOS - Working with autolayout and drawRect

I'm working with autolayout and I have a RoundView class (subview of UIButton) which drawRect method is:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat lineWidth = 0.0;
CGContextSetLineWidth(context, lineWidth);
CGContextSetFillColorWithColor(context, _currentBackgroundColor.CGColor);
CGContextAddEllipseInRect(context, self.bounds);
CGContextFillPath(context);
}
Then I add it to self (a UIView), set its width and height using autolayout. Everything looks great.
But when I change its size constraints (to have a bigger view), and call
[self layoutIfNeeded];
in an animation block, the circle pixels look bigger and ugly.
Do I do this the correct way ?
I just got it from this post !
The problem was that the os wasn't calling drawRect at each animation step.
To force it, in RoundView's init:
self.contentMode = UIViewContentModeRedraw;
You need to take the content scale into account for the retina displays [UIDevice mainScreen].scale. See answers to this (question)[CGContext and retina display

UIButton style settings are ignored when I override 'drawRect'

I have created a custom iOS numeric keyboard where the buttons have rounded corners:
control.layer.cornerRadius = 6;
control.layer.opacity = 1;
On one of the buttons I want to draw an image, and I've subclassed the UIButton in order to override the 'drawRect' function.
#interface KfKeypadButtonWithDrawing : UIButton
- (void)drawRect:(CGRect)rect;
#end
The drawRect implementation just draws some lines on the button. Nothing special.
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
// custom drawing code to create an image on the button
}
However, as soon as I override drawRect, the button ceases to have rounded corners. Is there any way to draw an image on the button AND have the button respect the cornerRadius style setting?
Thanks,
Aaron
Try to put the things you try to draw in drawRect into an image (png, jpg) and use UIButtons setImage:forState or setBackgroundImage:forState.
Yes, like this :
override func drawRect(rect: CGRect) {
// the custom drawing
let roundedRect = UIBezierPath(roundedRect: rect, cornerRadius: 20)
UIColor.redColor().setFill()
roundedRect.fill()
// call super
super.drawRect(rect)
}

drawRect Doesn't want to draw anything

I have a ViewController on storyboard. I have used the interface builder to set a toolbar at the bottom of the screen. I have set the custom view to a view overrides drawRect. However, for the life of me, I cannot get anything ever to show up on screen called from that drawRect. drawRect itself is called just fine, but nothing shows up on screen.
Also, I have this ViewController with a method that uses AVCaptureSession to toggle its background to a live view from camera input. I had suspected that this might have been the cause for error, but after removing all references of AVCaptureSession, I still cannot get this to work.
Sorry for my writing and/or lack of logic, I don't have any sleep right now.
Edit: Here is a small example of code that won't work. Every method inside gets called, but nothing is to show.
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 2.0);
CGContextMoveToPoint(context, 0,0); //start at this point
CGContextAddLineToPoint(context, 100, 100); //draw to this point
// and now draw the Path!
CGContextStrokePath(context);
}
The documentation for UIView says to not call [super drawRect] if you're overriding UIView. Remove that call! It's been known to cause strange behaviour.
According to the docs:
If you subclass UIView directly, your implementation of this method
does not need to call super. However, if you are subclassing a
different view class, you should call super at some point in your
implementation.

Resources