Very new to Swift and iOS development. What is the correct way to customise a UIButton?
I am looking to make a UI button that is just a circle, but I want to have the same behaviour as the system buttons: it fades when highlighted, and the fading is animated.
Ideally I would do this without images, and use UIBezierPath, so I can keep a consistent border width for different sizes.
To make a circular button, you can just use the corner radius property! So long as the frame/bounds of the button are a square, if you set the cornerRadius to half of the width, it'll appear as a circle. First, you'll need to import the QuartzCore framework:
import QuartzCore
Corner Radius
To set the corner radius:
myButton.layer.cornerRadius = 8.0
To make a circle given an arbitrary frame, you can use the following:
myButton.layer.cornerRadius = myButton.frame.size.width / 2.0
You may have to set the clipsToBounds property for the corner radius change to be visible:
myButton.clipsToBounds = true
Border
You can also use the layer property to set your border:
myButton.layer.borderWidth = 1.0
myButton.layer.borderColor = UIColor.purpleColor()
(See this post for more examples in Objective-C)
Related
Setting a UIView's corner radius can be done the following ways:
Set the layer's cornerRadius property:
view.layer.cornerRadius = 5;
view.layer.masksToBounds = true;
Apply a mask:
func roundCorners(corners:UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
Override draw(_:):
func draw(_ rect: CGRect) {
// Size of rounded rectangle
let rectWidth = rect.width
let rectHeight = rect.height
// Find center of actual frame to set rectangle in middle
let xf: CGFloat = (self.frame.width - rectWidth) / 2
let yf: CGFloat = (self.frame.height - rectHeight) / 2
let ctx = UIGraphicsGetCurrentContext()!
ctx.saveGState()
let rect = CGRect(x: xf, y: yf, width: rectWidth, height: rectHeight)
let clipPath = UIBezierPath(roundedRect: rect, cornerRadius: rectCornerRadius).cgPath
ctx.addPath(clipPath)
ctx.setFillColor(rectBgColor.cgColor)
ctx.closePath()
ctx.fillPath()
ctx.restoreGState()
}
Which of these is generally considered to be the "correct" way of implementing rounded corners on a UIView, accounting for the following criteria:
configuration (some corners may be rounded while others are not)
animation (can you animate the cornerRadius changing)
flexibility (does it break third party libraries or masks you have already applied)
readability (how concise/reusable is the solution)
speed (does it negatively impact performance)
Note that I don't know what's currently the “correct” way to set a UIView's corner radius.
What I prefer to do is to use Interface Builder as much as possible without having extra code which this approach shows and is reliable to my experience.
From iOS 11 upwards
you can use user-defined runtime attributes in the Identity inspector of the Interface Builder by setting the following properties:
layer.cornerRadius
layer.maskedCorners
layer.masksToBounds
According to the documentation of the CACornerMask you can see that the maskedCorners property is in fact a NSUInteger data type and you're allowed to set the following values:
kCALayerMinXMinYCorner = 1U << 0
kCALayerMaxXMinYCorner = 1U << 1
kCALayerMinXMaxYCorner = 1U << 2
kCALayerMaxXMaxYCorner = 1U << 3
Since you're allowed to bitwise OR those masks together you only have to "calculate" the resulting integer of that bitwise OR of what you actually need.
Therefore set the following number (integer) values for the maskedCorners property to get rounded corners:
0 = no corner is being rounded
1 = top left corner rounded only
2 = top right corner rounded only
3 = top left and top right corners rounded only
4 = bottom left corner rounded only
5 = top left and bottom left corners rounded only
6 = top right and bottom left corners rounded only
7 = top left, top right and bottom left corners rounded only
8 = bottom right corner rounded only
9 = top left and bottom right corners rounded only
10 = top right and bottom right corners rounded only
11 = top left, top right and bottom right corners rounded only
12 = bottom left and bottom right corners rounded only
13 = top left, bottom left and bottom right corners rounded only
14 = top right, bottom left and bottom right corners rounded only
15 = all corners rounded
Example: If you want to set the corner radius for the top-left and the top-right corners of a UIView you would use those attributes:
Re your three options:
Using CALayer existing properties: This is an easy (and likely the most efficient) solution for simple corner masking. It is animatable, too. In iOS 11 and later, you can pick which corners are to be masked.
Re custom CAShapeLayer masks: This is nice approach if the corner masking is not simple corner rounding but some arbitrary path. You have to be cautious to make sure to update this mask if the frame changes (e.g. update the path in layoutSubviews of view or in viewDidLayoutSubviews of controller).
Admittedly, if you want to do a very graceful animation as the view’s frame changes, that takes a little more work. But, as I point out above, simply responding to frame changes in layoutSubviews or viewDidLayoutSubviews is quite simple and takes care of it if you are not too worried about the corner rounding mid-animation.
Re custom draw(_:): This is more work than it is worth and you are probably not enjoying optimizations that Apple’s team may have done behind the scenes (e.g. what if subsequent draw calls are only drawing a portion of the full bounds; your code is redrawing the whole thing regardless).
I would suggest option 1 for simple cases, and option 2 if you need more control than option 1 can offer. But there is no “best” approach: It depends upon what you need and how much work you are willing to go through.
I did a couple of test with iOS 11 or lower version and the best practice I discovered for me to round a specific or all corners, you can do with the next code.
// Full size
CGSize vSize = [UIScreen mainScreen].bounds.size;
// Object
UIView *viewTest = [[UIView alloc] initWithFrame: CGRectMake(0, 0, vSize.width, vSize.height)];
[viewTest setAutoresizingMask: UIViewAutoresizingFlexibleHeight];
[viewTest setBackgroundColor: [UIColor grayColor]];
// maskedCorners is only available in iOS 11
if (#available(iOS 11.0, *)) {
[viewTest setClipsToBounds: YES];
[viewTest.layer setCornerRadius: 10];
// Only if you want to round the left and right top corners
[viewTest.layer setMaskedCorners: kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner];
}
else {
// The old way used in lower version
CAShapeLayer *shapeLayerObj = [CAShapeLayer layer];
[shapeLayerObj setPath: [UIBezierPath bezierPathWithRoundedRect: viewTest.bounds byRoundingCorners: UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii: (CGSize){10.0, 10.}].CGPath];
[viewTest.layer setMask: shapeLayerObj];
}
This code fix the problem with autoResizingMask doesn't work when it use the old way to round corners.
Fix a bug with UIScrollView with round corners and setContentSize major to height of object.
The swift version it's something like this
let view = UIView()
view.clipsToBounds = true
view.layer.cornerRadius = 8
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
I think this is the most comprehensive summary of all: http://texturegroup.org/docs/corner-rounding.html
My heuristic is that if the view doesn't need a high performance (e.g. it's not inside a table view cell), the easiest option is using CALayer's cornerRadius. If you need some more advanced corner radius or high performance, then it's better to explore other options.
I go with the first one, it is the cleaner way of doing and you can do it in the IDE without code. Open the attributes inspector and then click on the Identity inspector and add under "User Defined Runtime attributes" those 2 properties:
contView.layer.cornerRadius = 25
contView.layer.maskedCorners = [.layerMaxXMinYCorner,.layerMinXMinYCorner]
contView.layer.masksToBounds = true
this is the result just top left and top right corners
This question already has answers here:
Adding border with width to UIView show small background outside
(4 answers)
Closed 5 years ago.
I'm setting the background color to a white border and the border is seeping through the background.I'm looking to have the blue line gone. Can you please suggest a solution to this problem?
Here is my code below. And there is an image of what I'm talking about below.
[
self.imageView.layer.borderColor = UIColor.white.cgColor
self.imageView.layer.borderWidth = 3
self.imageView.backgroundColor = UIColor.blue
self.imageView.layer.cornerRadius = CGFloat(CircleDiameter/2)
I guess this is what iOS rendering do with a layer have positive cornerRadius, I have Tested add a white circle view overlap a blue circle view which has the same size, the same situation appears:
In fact, through borderWidth Document the border of layer is drawn inset from the receiver’s bounds, so it has the same kind of situation as I mentioned above:
When this value is greater than 0.0, the layer draws a border using the current borderColor value. The border is drawn inset from the receiver’s bounds by the value specified in this property. It is composited above the receiver’s contents and sublayers and includes the effects of the cornerRadius property.
So borders + roundness = sadness on iOS and I think you need another way to implement your design.
I have 3 UITextFields with border style none. I want to add borders in code. The effect I want to achieve is to have rounded top corners on first UITextField and to have rounded bottom corners on third text field. Code I am using for rounding edges is here Round top corners of a UIView and add border
But i get this - no right edge and corners are not rounded:
Note: I've set all constraints, that is not a problem. If i use UITextBorderStyleLine right edge is not rounded again.
Please help.
if you want to simplest way to do like on a screen look here>>>
Grey view with clip subviews mode on, and 3 labels/textfields inside, and 2 black view with 1 pixel height
in code..
self.viewCorner.layer.cornerRadius = 6;
self.viewCorner.layer.borderWidth = 1;
self.viewCorner.layer.borderColor = [UIColor blackColor].CGColor;
After you set constraints to grey view and 2 views with 1 pixel height like this
Grey view
1 pixel height view
and result on IPad simulator
Thats all, you can do this for 5 minutes
You need to create custom UItextField or method to change the top and bottom corner to oval shape. Here is a below sample code to top corner similarly you need to do it for bottom left and right corner.
CGRect rect = myTextField.bounds;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect
byRoundingCorners:UIRectCornerTopLeft |UIRectCornerTopRight
cornerRadii:CGSizeMake(6.0, 6.0)];
CAShapeLayer *layers = [CAShapeLayer layer];
layers.frame = rect;
layers.path = path.CGPath;
myTextField.layer.mask = layers;
You will notice the red "+" / "arrows" glyph in the attached screenshot. It is easy enough to change this "origin" point in Xcode. Is there also a way to do this programmatically or is this entirely an Xcode abstraction?
For instance, I want to programmatically create a UILabel and position it by calculating the lower right hand coordinate. In Xcode, I would simply make sure that the red "+" is on the bottom right grid point and define the X, Y, Width and Height parameters with that "origin" in mind.
If you're not using autolayout, you can position a label (or any view) in code by setting its center. So if you know where you want the label's lower right corner to be, you can just subtract half the width and height of the label to compute where its center should be:
CGPoint lowerRight = somePoint;
CGRect frame = label.frame;
label.center = CGPointMake(lowerRight.x - frame.size.width / 2,
lowerRight.y - frame.size.height / 2);
I would recommend just doing that.
But if you want, you can instead go to a lower level. Every view has a Core Animation layer, which is what actually manages the view's on-screen appearance. The layer has an anchorPoint property, which by default is (0.5, 0.5), representing the center of the layer. You can set the anchorPoint to (1, 1) for the lower-right corner:
label.layer.anchorPoint = CGPointMake(1, 1);
Now the label's center actually controls the location of its lower right corner, so you can set it directly:
label.center = somePoint; // actually sets the lower right corner
You'll need to add the QuartzCore framework to your target and import <QuartzCore/QuartzCore.h> to modify the anchorPoint property.
myObject.origin = CGPointMake (0.0,0.0);
I'm trying to draw a custom button frame as follows:
UIBezierPath *stroke = [UIBezierPath bezierPathWithRoundedRect:self.bounds
cornerRadius:RECT_CORNECR_RADIUS];
[stroke stroke];
But for some reason the corner curves look thinker than the sides. If you look at the UIButton's default frame it's very uniform. A is a UIButton, B is a custom button.
Any ideas how I can make it more like the UIButton.
You are stroking the bounds of your button. This will draw your line centred over the edge the view, so half of the thickness of the line is outside the bounds and is not drawn. This is why it is full thickness in the corners. Use CGRectInset on your bounds rectangle (inset by half the thickness of your line) and stroke THAT rect.
The problem you have is probably due to antialiasing. You can try to change the antialiasing settings of CoreGraphics before drawing your beizerPath.
An easier solution is to use the CALayer of your button and its cornerRadius property. It would be easier to draw a rounded corner
If self is your custom button:
self.layer.cornerRadius = RECT_CORNER_RADIUS;
self.layer.borderWidth = 1.0f;
self.layer.borderColor = [UIColor blackColor].CGColor;
Of course don't forget to import the QuartzCore framework and import its header for this to work (#import )