UIButton style settings are ignored when I override 'drawRect' - ios

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)
}

Related

Create round UIButton with custom drawRect: method

My goal is to create a custom UIButton subclass, that creates a round (or round rect) button with some additional features.
After some searching, I found that simply setting the cornerRadius of the button layer is the easiest way to make the button round:
#implementation MyRoundButton
-(id)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame])
[self setupView];
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
if(self = [super initWithCoder:aDecoder])
[self setupView];
return self;
}
-(void)setupView {
self.layer.cornerRadius = MIN(self.frame.size.height, self.frame.size.width) / 2.0;
}
This works fine to, as long as I do not override drawRect:
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
}
Of course I would like to add some custom drawing code to drawRect: but even if only [super drawRect:rect] the button is not round anymore: It drawn as rectangle within its bounds.
How is this possible? How can overriding a method with a simple super call change the behavior at all?
How can I avoid this problem? I already tried to let the layer unchanged and to simply draw the background manually in drawRect:. However the button draws its background rect anyway and my custom drawing is on top of it.
So, how to solve this?
The layer in your example conceptually contains a background with a corner radius, but the empty override of drawRect: causes that to be ignored. That's because, ordinarily, an instance of UIView depends on its built-in layer (an instance of CALayer) to render its content, so drawRect: isn't called. However, the layer is capable of delegating drawing behavior to its view, and will do so if you implement drawRect:.
If you're simply subclassing UIView, this shouldn't present any problem. However, subclassing a framework component such as UIButton is a bit dicier. One potential issue is the call to -[super drawRect:]; it's hard to know precisely what mischief that might cause, but that may be the source of the problem. By default, a UIButton doesn't need to do any custom drawing; it contains a nested instance of a private class, UIButtonLabel, that draws the button's title.
Instead of trying to override the drawing behavior of a class whose inner details are private, consider using one or more static images and simply setting the button's background image property for one or more states.
See if this changes anything...
-(void)setupView {
self.layer.cornerRadius = MIN(self.frame.size.height, self.frame.size.width) / 2.0;
self.clipsToBounds = YES;
}

How to make a fade out border for uiview

How to implement a one-side and fade out border for UIView. I'm sorry that I don't have enough reputation to post a screenshot. But there is an example in iOS native Contact app. If you click on "+" then "add phone", there will be a vertically line, which is exactly what I want.
I have already implemented one-side border for an UIView but can't find out a way to make it fade out. I tried CIFilter, which didn't work. Then I noticed that this fade out effect is simliar to shadow. I tried to simulate the fade out by setting shadow but failed again.
Thanks for any answer and suggestion in advance.
You could use a CAGradientLayer:
let layer = CAGradientLayer()
layer.colors = [UIColor.white.cgColor, UIColor.gray.cgColor]
// play around with layer.locations and layer.{startPoint|endPoint} to suit your needs
You can then subclass UIView and override its layerClass to return the gradient layer, and then
size and position that view as you please:
override class func layerClass() -> AnyClass {
return CAGradientLayer.self
}
// configure the layer with color, end/start and locations in init()
This allows you to drop that view in anywhere and use auto layout to size and position it as you please.
You can create a CAGradientLayer, but instead of subclassing the UIView, just set it to the mask property of the layer:
let gradientMaskLayer = CAGradientLayer()
gradientMaskLayer.frame = maskedView.bounds
gradientMaskLayer.colors = [UIColor.clear.cgColor, UIColor.white.cgColor]
gradientMaskLayer.locations = [0.8, 1]
maskedView.layer.mask = gradientMaskLayer

Can I resize a bezier path like a regular view in swift?

I'm trying to resize a bezier drawn circle in an app I am making for iPad. The only way I know of so far is to use CATransform3DMakeScale. But, is there a way in which I can simply specify a height or width for the circle's shape layer and it will resize it to fit my specifications. Simply because I think this will be a lot more easy and consistent than using a scale all the time.
If I understood you correctly, you want the circle to fit the UIView it's drawn in?
If you are creating your circle as a subclass of UIView, you can do this by using methods setNeedsDisplay and drawRect.
class CircleView: UIView {
let radius: CGFloat = min(self.frame.width, self.frame.height) / 2 {
didSet {
setNeedsDisplay()
}
}
override func drawRect(rect: CGRect) {
// draw your UIBezierPath here
}
}
First you get the radius for your circle from views width or height, depending on which one is smaller. Then you call setNeedsDisplay() which will do what's inside drawRect.
For more information, please take a look at class references for both classes:
UIView
UIBezierPath

Circular UIView (with cornerRadius) without Blended Layer

I'm trying to get the circle below to have an opaque solid white color where the cornerRadius cuts out the UIView.
UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(i * (todaySize + rightMargin), 0, smallSize, smallSize)];
circle.layer.cornerRadius = smallSize/2;
circle.layer.borderWidth = 0.5;
circle.layer.backgroundColor = [UIColor whiteColor].CGColor;
circle.backgroundColor = [UIColor whiteColor];
[self addSubview:circle];
I've tried a few things like setting the backgroundColor and opaque without any luck. Color Blended Layers still shows that the surrounding of the circle is transparent. Does anybody know how to solve this?
To avoid blending when using rounded corners, the rounding needs to be done in drawRect, rather than as a property on the layer. I needed UICollectionView cells with a rounded background in an app I'm working on. When I used layer.cornerRadius, the performance took a huge hit. Turning on color blended layers yielded the following:
Not what I was hoping for, I want those cells to be colored green indicating there is no blending occurring. To do this, I subclassed UIView into RoundedCornerView. My implementation is real short and sweet:
import UIKit
class RoundedCornerView: UIView {
static let cornerRadius = 40.0 as CGFloat
override func drawRect(rect: CGRect) {
let borderPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: RoundedCornerView.cornerRadius)
UIColor.whiteColor().set()
borderPath.fill()
}
}
Then I set the view I was rounding to be a RoundedCornerView in my nib. Running at that point yielded this:
Scrolling is buttery smooth and there is no longer any blending occurring. One odd side effect of this is that the view's backgroundColor property will color the excluded area of the corners, not the main body of the view. This means that the backgroundColor should be set to whatever is behind your view, not to the desired fill color.
Try using a mask to both avoid blending and dealing with the parent / child background color match.
override func layoutSubviews() {
super.layoutSubviews()
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 20).cgPath
layer.mask = maskLayer
}
Set clipsToBoundson the view or masksToBounds on the layer to YES

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

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

Resources