I want to create a custom button in swift and the result is not quite satisfying. UIBezierpath is not drawing a smooth path. Please see image and code below.
let roundRect = UIBezierPath(roundedRect: CGRectMake(0, 0, 156, 60), byRoundingCorners:.AllCorners, cornerRadii: CGSizeMake(200, 200))
After playing around a little bit, I found that moving the path down and right 1 point and shrinking the rectangle size by 1 will solve the problem. but I don't like this solution, I don't think this is the correct way of doing it. And I feel like the frame is clipping my image. Can someone help me with this??
let roundRect = UIBezierPath(roundedRect: CGRectMake(1, 1, 154, 59), byRoundingCorners:.AllCorners, cornerRadii: CGSizeMake(200, 200))
the whole CustomButton Subclass:
import UIKit
class DesignableButtons: UIButton {
let black = UIColor(red: 33/255, green: 33/255, blue: 33/255, alpha: 1.0) /* #212121 */
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.setTitle("Go to Google", forState: UIControlState.Normal)
self.titleLabel!.font = UIFont(name: "GT Walsheim", size: 14)
self.setTitleColor(black, forState: .Normal)
self.setTitleColor(UIColor.whiteColor(), forState: .Highlighted)
self.setTitleColor(UIColor.whiteColor(), forState: .Selected)
self.titleLabel!.alpha = 1.0
}
let roundRect = UIBezierPath(roundedRect: CGRectMake(0, 0, 156, 60), byRoundingCorners:.AllCorners, cornerRadii: CGSizeMake(200, 200))
override func drawRect(rect: CGRect) {
black.setStroke()
roundRect.stroke()
if highlighted {
black.setFill()
roundRect.fill()
}
}
override var highlighted: Bool {
didSet {
setNeedsDisplay()
}
}
}
Try this
let roundRect = UIBezierPath(roundedRect: CGRectInset(CGRectMake(0, 0, 156, 60), 1, 1), byRoundingCorners:.AllCorners, cornerRadii: CGSizeMake(200, 200))
EDITED
or you can also do this to do it more reusable, this in your layoutSubviews
let roundRect = UIBezierPath(roundedRect: CGRectInset(self.frame, 1, 1), byRoundingCorners:.AllCorners, cornerRadii: CGSizeMake(200, 200))
Instead of
let roundRect = UIBezierPath(roundedRect: CGRectMake(0, 0, 156, 60), byRoundingCorners:.AllCorners, cornerRadii: CGSizeMake(200, 200))
the CGRectInset function create a rect inside your original rect,
Returns a rectangle that is smaller or larger than the source
rectangle, with the same center point.
EDITED
you can put this in your init to fix the visualization issue
self.contentScaleFactor = UIScreen.mainScreen().scale //SUPPORT RETINA
I hope this help you
You have problem because lines was been clipped. When you drawing into drawRect: this happen an always. Try drawing outside drawRect:.
For example:
required init?(coder aDecoder: NSCoder) {
// some code
layer.masksToBounds = true
layer.cornerRadius = 200
layer.borderWidth = 1
layer.borderColor = black.CGColor
}
Or if you want using sublayers:
let borderLine = CAShapeLayer()
required init?(coder aDecoder: NSCoder) {
// some code
frame = someFrame
let roundRect = UIBezierPath(roundedRect: CGRectMake(0, 0, 156, 60), byRoundingCorners:.AllCorners, cornerRadii: CGSizeMake(200, 200))
borderLine.path = roundRect.CGPath
borderLine.frame = bounds
borderLine.strokeColor = black.CGColor
borderLine.fillColor = UIColor.clearColor().CGColor
borderLine.lineWidth = 1
layer.insertSublayer(borderLine, atIndex: 0)
}
Related
I wanted to design a rounded button with a shadow for my iOS project in Swift. So I came up with the following custom button class:
import UIKit
#IBDesignable class MainButton: UIButton {
private var shadowLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
let image = createBackgroundImage()
setBackgroundImage(image, for: UIControl.State.normal)
clipsToBounds = true
contentEdgeInsets = UIEdgeInsets(top: 5, left: 20, bottom: 5, right: 20)
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 3)
layer.shadowOpacity = 0.2
layer.shadowRadius = 6
}
func createBackgroundImage() -> UIImage {
let rect = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
let color = UIColor.white
color.setFill()
UIBezierPath(roundedRect: rect, cornerRadius: frame.height * 0.5).addClip()
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
As soon as I set this class for a button in my Storyboard the "IBDEsignablesAgent-iOS" process takes nearly 100% of my CPU and Xcode hangs it self. This behavior makes it near impossible to debug the problem correctly.
I'm quite certain I do something in the wrong order or wrong method. But I have no clue how to solve it. Hopefully someone here can point me the right direction.
Thanks,
Jens
Simply check that the size of the view actually changed:
private var lastSize: CGSize = .zero
override func layoutSubviews() {
super.layoutSubviews()
guard frame.size != lastSize else { return }
lastSize = frame.size
...
}
Creating shadows is slow and layoutSubviews is called tens of times every second (basically, once every frame).
My custom button with some shadow and rounded corners, I use it directly within the Storyboard, no need to touch it programmatically.
class RoundedCornerButtonWithShadow: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
self.layer.masksToBounds = false
self.layer.cornerRadius = self.frame.height/2
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
self.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
self.layer.shadowOpacity = 0.5
self.layer.shadowRadius = 1.0
}
}
I'm trying to make round corner for topLeft and rightBottom, the top round work well but leftBottom still rectangle,
I searched a lot I think it's overlap issue but don't know how to fix it, please any help?
P.S. it's work well just in IPhone Xs Max
import UIKit
#IBDesignable
class CustomView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setRadius()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setRadius()
}
override func prepareForInterfaceBuilder() {
setRadius()
}
func setRadius()
{
let cornerRadius = 18.0
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: [.topLeft, .bottomRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
let maskLayer = CAShapeLayer()
self.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
maskLayer.frame = self.bounds
maskLayer.path = path.cgPath
self.layer.mask = maskLayer
}
}
Try this
let your_Frame = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))//define your frame size you want
your_Frame.layer.cornerRadius = 25//deine the radius you want
your_Frame.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMaxYCorner]
view.addSubview(your_Frame)
I want to round top and bottom corners of 2 UITableViewCell like this : It's what I get on iOS 11
What I want
But I got this on iOS 10:
Result rectShape.bounds = self.bounds
With that code :
UITableViewCell of blue view
import UIKit
class ProductDescriptionTableViewCell: UITableViewCell {
var ProductDescription: UITextView!
public var container: UIView!
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
// Set the width of the cell
self.bounds = CGRect(x: self.bounds.origin.y + 16, y: self.bounds.origin.y, width: self.bounds.size.width - 16, height: self.bounds.size.height)
super.layoutSubviews()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.backgroundColor = UIColor.blue
if #available(iOS 11.0, *) {
self.layer.masksToBounds = true
self.layer.cornerRadius = 10
self.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
} else {
let rectShape = CAShapeLayer()
rectShape.bounds = self.bounds// CGRect(x: self.bounds.origin.x + 16, y: self.bounds.origin.y, width: self.bounds.size.width - 16, height: self.bounds.size.height)
rectShape.position = self.center
rectShape.path = UIBezierPath(roundedRect: rectShape.bounds, byRoundingCorners: [.bottomRight, .bottomLeft], cornerRadii: CGSize(width: 10, height: 10)).cgPath
self.layer.backgroundColor = UIColor.red.cgColor
//Here I'm masking the textView's layer with rectShape layer
self.layer.mask = rectShape
}
}
}
UITableViewCell of purple view
import UIKit
class ProductTitleTableViewCell: UITableViewCell {
var ProductTitle: UILabel!
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
// Set the width of the cell
self.bounds = CGRect(x: self.bounds.origin.y + 16, y: self.bounds.origin.y, width: self.bounds.size.width - 16, height: self.bounds.size.height)
super.layoutSubviews()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.backgroundColor = UIColor.purple
if #available(iOS 11.0, *) {
self.layer.masksToBounds = true
self.layer.cornerRadius = 10
self.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
} else {
let rectShape = CAShapeLayer()
rectShape.bounds = self.bounds//CGRect(x: self.bounds.origin.x + 16, y: self.bounds.origin.y, width: self.bounds.size.width - 16, height: self.bounds.size.height)
rectShape.position = self.center
rectShape.path = UIBezierPath(roundedRect: rectShape.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 10, height: 10)).cgPath
self.layer.backgroundColor = UIColor.green.cgColor
//Here I'm masking the textView's layer with rectShape layer
self.layer.mask = rectShape
}
}
}
if I replace self.bounds by the CGRect in comment I got that :
With rectShape.bounds = CGRect(x: self.bounds.origin.x + 16, y: self.bounds.origin.y, width: self.bounds.size.width - 16, height: self.bounds.size.height)
I try to change the self.bounds at the beginning of init() but it's doesn't work too
And I also try to put the code which change corners in layoutSubviews() but in that case the UITableViewCell disappear
I don't one to use only one UITableViewCell and round his corners I really need to round bottom and top corners on to separate UITableViewCell
If you know how I can fix that I will be very grateful
Thank you for your time
Add the following extension:
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
}
Then call it from layout subviews like below:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
headerView.roundCorners([UIRectCorner.topLeft, UIRectCorner.topRight], radius: kTableViewCornerRadius)
}
Well, i am using this very simple extension of UIView to make corners round. In cellForRowAt delegate method, after dequeing the cell, just call
cell.makeCornersRound()
extension UIView {
public func makeCornersRound () {
self.layer.cornerRadius = 8
self.clipsToBounds = true
}
}
I need to draw a shape like that one:
And animate the color path when you are scrolling. I have been for 2 hours trying several things but don't reach the final way to overcome that. I have tried creating a custom UIView, create a CShapeLayer and then a UIBezierPath to that layer, and finally adding the subview to a view in the viewdidload, but this does not work. What else can I do to approach that? I will start with the shapes that are the most complex things, as the label will be just aligned to the shape.
----FIRST PART SOLVED, DRAWRECT NOW APPEARS, BUT HOW DO I ANIMATE?----
That's my updated code in my drawRect method.
class CustomOval: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override func drawRect(rect: CGRect) {
//SHAPE 2
let rectanglePath2 = UIBezierPath(roundedRect: CGRectMake(135, 177, 20, 70), cornerRadius: 0)
let shapeLayer2 = CAShapeLayer()
shapeLayer2.path = rectanglePath2.CGPath
shapeLayer2.bounds = CGRect(x: 135, y: 177, width: 20, height: 70)
shapeLayer2.lineWidth = 5.0
let label2 = UILabel()
label2.frame = CGRectMake(150 + rectanglePath2.bounds.width, rectanglePath2.bounds.height + 120, 100, 50)
label2.text = "Label 2"
self.addSubview(label2)
//// Rectangle Drawing
UIColor.blueColor().setFill()
rectanglePath2.fill()
//SHAPE 3
let rectanglePath3 = UIBezierPath(roundedRect: CGRectMake(135, 237, 20, 70), cornerRadius: 0)
let shapeLayer3 = CAShapeLayer()
shapeLayer3.path = rectanglePath3.CGPath
shapeLayer3.bounds = CGRect(x: 135, y: rectanglePath2.bounds.maxY, width: 20, height: 70)
shapeLayer3.lineWidth = 5.0
//// Rectangle Drawing
UIColor.redColor().setFill()
rectanglePath3.fill()
let label3 = UILabel()
label3.frame = CGRectMake(rectanglePath3.bounds.width + 150, rectanglePath3.bounds.height + 190, 100, 50)
label3.text = "Label 3"
self.addSubview(label3)
//SHAPE 1
let rectanglePath = UIBezierPath(roundedRect: CGRectMake(104, 24, 80, 155), cornerRadius: 40)
let shapeLayer = CAShapeLayer()
shapeLayer.path = rectanglePath.CGPath
shapeLayer.bounds = CGRect(x: 104, y: 24, width: 80, height: 155)
shapeLayer.lineWidth = 5.0
//// Rectangle Drawing
UIColor.grayColor().setFill()
rectanglePath.fill()
}
}
-UPDATE ANIMATION-
var shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
shapeLayer = CAShapeLayer()
super.init(frame: frame)
animateShape1()
}
required init(coder aDecoder: NSCoder) {
shapeLayer = CAShapeLayer()
super.init(coder: aDecoder)!
animateShape1()
}
func animateShape1(){
let animation = CABasicAnimation(keyPath: "fillColor")
animation.fromValue = UIColor.whiteColor().CGColor
animation.toValue = UIColor.redColor().CGColor
animation.duration = 5 //2 sec
shapeLayer.fillColor = UIColor.redColor().CGColor //color end value
layer.addAnimation(animation, forKey: "somekey")
}
My main questions now are:
How do I set an image inside the CAShapeLayer? I have tried with: FIXED
Calling it from the init
func addImage(){
let imageSubLayer = CALayer()
let image = UIImage(named: "paint.png")
imageSubLayer.contents = image?.CGImage
imageSubLayer.bounds = (frame: CGRect(x: shapeLayer.bounds.width/2, y: shapeLayer.bounds.height/2, width: 50, height: 50))
shapeLayer.addSublayer(imageSubLayer)
}
I have also tried, and the one that has worked is: But i dont want a tiled image. I have also tried without tiled and this does not work
CGContextSaveGState(context)
rectanglePath.addClip()
CGContextScaleCTM(context, 0, (image?.size.height)!)
CGContextDrawTiledImage(context, CGRectMake(20, 20, 50, 50), image!.CGImage)
CGContextRestoreGState(context)
I also need to animate the process, and I have tried with the reply
of #s0urce but unfortunately, it is not drawing. Do I need to do
something else?
You should never add sublayers inside drawRect method. It would be much better to do this inside init or initWithFrame: method of a view or at least inside viewDidLoad method of view controller.
You need to set bounds of CAShapeLayer otherwise it would be CGRectZero. Don't forget that points of UIBezierPath should be in the coordinate system of CAShapeLayer.
Color animation sample code:
let animation = CABasicAnimation(keyPath: "fillColor")
animation.fromValue = UIColor.whiteColor().CGColor
animation.toValue = UIColor.redColor().CGColor
animation.duration = 2 //2 sec
layer.fillColor = UIColor.redColor().CGColor //color end value
layer.addAnimation(animation, forKey: "somekey")
I've created an uiview in my xib with background color as clear color. When I apply the shadow on the layer of the view, the shadow is not appearing. But when i set the background color other than clear color, shadow is showing. Please help.
this is my code
self.cView.layer.shadowColor=[UIColor whiteColor].CGColor;
self.cView.layer.shadowOffset=CGSizeZero;
self.cView.layer.shadowRadius=30.0;
self.cView.layer.shadowOpacity=1.0;
self.cView.layer.cornerRadius=10.0;
The problem is, that shadow actually takes into account the 'upper' layer. If there's nothing on it there will be no shadow: How Shadows Work
EDIT:
There is this recipe copied from paste bin
view.layer.shadowColor = [UIColor colorWithWhite:.5 alpha:1].CGColor;
view.layer.shadowRadius = 4.0f;
view.layer.shadowPath = CGPathCreateWithRect(CGRectMake(0, 0, 50, 50), NULL);
view.layer.shadowOpacity = 1.0f;
view.layer.shadowOffset = CGSizeMake(1, 1);
But I doubt this will be of any use to you: the result is a view 'painted' with color of a shadow and a shadow around it.
If you specify shadowPath property
shadowView.layer.shadowPath =
UIBezierPath(
roundedRect: shadowView.bounds,
cornerRadius: 10).cgPath
(Or whatever corner radius is desired.)
it will work even with .clear backgroundColor.
Note that you of course have to do this in layoutSubviews of the view in question.
Here's an actual full working example:
import UIKit
#IBDesignable class LonelyShadow: UIView {
let corner: CGFloat = 20
override init(frame: CGRect) {
super.init(frame: frame)
common()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
common()
}
private func common() {
backgroundColor = .clear
clipsToBounds = false
layer.shadowColor = UIColor.yourColor.cgColor
layer.shadowOffset = CGSize(width: 0, height: 25)
layer.shadowOpacity = 0.3
layer.shadowRadius = 40
}
override func layoutSubviews() {
super.layoutSubviews()
layer.shadowPath = UIBezierPath(
roundedRect: bounds, cornerRadius: corner).cgPath
}
}
Equivalent to #Rok Jark's answer in Swift 4:
self.layer.shadowColor = UIColor(white: 0.5, alpha: 1).cgColor
self.layer.shadowRadius = 4.0
self.layer.shadowPath = CGPath.init(rect: CGRect.init(x: 0, y: 0, width: 50, height: 50), transform: nil)
self.layer.shadowOpacity = 1.0;
self.layer.shadowOffset = CGSize(width: 1, height: 1)