BezierPath sublayer does not wrap around the whole UIView - ios

I'm currently trying to make a dotted border around my UIView. I referred to a previous post: Dashed line border around UIView
The left side of the UIView gets the red dotted lines but the right side misses the edge.
This is left side
This is right side
Here is my code which I am executing in viewDidLoad:
myview.backgroundColor = UIColor.lightGray
myview.layer.cornerRadius = 4
let dottedBorder = CAShapeLayer()
dottedBorder.strokeColor = UIColor.red.cgColor
dottedBorder.lineDashPattern = [4, 4]
dottedBorder.frame = myview.bounds
dottedBorder.fillColor = nil
dottedBorder.path = UIBezierPath(roundedRect: myview.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 4, height: 4)).cgPath
myview.layer.addSublayer(dottedBorder)

Assuming you're using autolayout, you shouldn't depend on the view size inside viewDidLoad, because usually it equals you storyboard device size(selected is SB editor), not the real device one.
Anyway, it may change in future. All calculations depending on frame/bounds needs to be done inside viewDidLayoutSubviews. Something like this:
private let dottedBorder = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
dottedBorder.strokeColor = UIColor.red.cgColor
dottedBorder.lineDashPattern = [4, 4]
dottedBorder.fillColor = nil
myview.layer.addSublayer(dottedBorder)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
dottedBorder.frame = myview.bounds
dottedBorder.path = UIBezierPath(roundedRect: myview.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 4, height: 4)).cgPath
}

Related

Bottom inner shadow with rounded corners

I'm trying to reproduce the following design, and I'm struggling with the bottom inner shadow.
I didn't found any solution that fulfill the requirements:
bottom only
contained in a rounded view
and ideally, a solution working with UIButton.
While this is possible, it's a bit tricky and it will require you to draw shadows using CGPath: https://developer.apple.com/documentation/quartzcore/calayer/1410771-shadowpath
Probably a simpler way would be to use a resizable image: https://developer.apple.com/documentation/uikit/uiimage/1624102-resizableimage
You would make a smaller image like this mock below, and then simply resize it to increase the frame like so:
let resized = mockImage.resizableImage(withCapInsets: UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 56), resizingMode: .stretch)
I have created a button extension for you to add the bottom shadow. Please check it out. You just need to call the "bottomShadow()" function with your button refrence
e.g.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.button?.bottomShadow()
}
/*Button Extension*/
extension UIButton {
func bottomShadow() {
let shadowHeight: CGFloat = 5.0
let shadowframe = CGRect.init(x: 0, y: self.bounds.height - shadowHeight, width: self.bounds.width, height: shadowHeight)
let path = UIBezierPath(roundedRect: shadowframe, byRoundingCorners: [.topLeft , .topRight], cornerRadii: CGSize(width: self.layer.cornerRadius, height: self.layer.cornerRadius))
let mask = CAShapeLayer()
mask.fillColor = UIColor.lightGray.cgColor
mask.path = path.cgPath
self.layer.addSublayer(mask)
}
}

Swift - Shadow for a irregular shape of a View

i am struggling to add shadow to a custom shape.
Here is a picture of what i want to construct:
(Dont mind the text and the symbol)
You can see the custom shape with the curved corner on the right and the rectangular shape on the left with shadow.
I am using UIView, and added corner to the left.
This is the code i have so far that shape the view correct:
View1.backgroundColor = .green //green color is just to see the shape well
let path = UIBezierPath(roundedRect:View1.bounds,
byRoundingCorners:[.topRight, .bottomRight],
cornerRadii: CGSize(width: self.frame.height/2, height: self.frame.height/2))
let maskLayer = CAShapeLayer()
I Have tried to add shadow to it, but the shadow does not apear.
Here is the code i have tried to add shadow:
View1.layer.masksToBounds = false
View1.layer.layer.shadowPath = maskLayer.path
View1.layer.shadowColor = UIColor.black.cgColor
View1.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
View1.layer.shadowOpacity = 0.5
View1.layer.shadowRadius = 1.0
How can you add shadow to this shape?
You can achieve this by using a single UIView (shadowView), adding a shapeLayer sublayer and setting the shadow of the shadowView's layer.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
#IBOutlet var shadowView: UIView!
func setup() {
// setup irregular shape
let path = UIBezierPath.init(roundedRect: shadowView.bounds, byRoundingCorners: [.topRight, .bottomRight], cornerRadii: CGSize.init(width: 20, height: 20))
let layer = CAShapeLayer.init()
layer.frame = shadowView.bounds
layer.path = path.cgPath
layer.fillColor = UIColor.white.cgColor
layer.masksToBounds = true
shadowView.layer.insertSublayer(layer, at: 0)
// setup shadow
shadowView.layer.shadowRadius = 8
shadowView.layer.shadowOpacity = 0.2
shadowView.layer.shadowOffset = CGSize.init(width: 0, height: 2.5)
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowPath = path.cgPath
}
}
Note:
The shadowView.clipToBounds must be false for the shadows to take effect.
To see the layer.fillColor, set the shadowView.backgroundColor to .clear.
You can easily achieve the above via Interface Builder by setting the 'Background' property and unchecking the 'Clip to Bounds' checkbox.

CornerRadius don't set

I'm trying to round the corners on the UIView in bottom left and bottom right.
extension UIView {
func roundBottom(raduis: CGFloat){
let maskPath1 = UIBezierPath(roundedRect: bounds,
byRoundingCorners: [.BottomRight, .BottomLeft],
cornerRadii: CGSize(width: raduis, height: raduis))
let maskLayer1 = CAShapeLayer()
maskLayer1.frame = bounds
maskLayer1.path = maskPath1.CGPath
layer.mask = maskLayer1
}
}
And call cell.bottomCorner.roundBottom(8)
But I get it:
iPhone 5:
iPhone 6s:
iPhone 6s Plus:
You have to update the mask everytime the view changes its size, therefore ideally you should change it whenever UIView.layoutSubviews is called:
override func layoutSubviews() {
super.layoutSubviews();
// update mask
}
It's not ideal to do it in an extension helper method. You should create a specific class to handle the size change.

rounding corners of UIbutton disturbing its width

I have three buttons in which I want to round:
Top corners of first button
Bottom corners of second button
All four corners of third button
I achieved this by the following code:
button1.roundedButton1()
button2.roundedButton2()
button3.layer.cornerRadius = 5
extension UIButton {
func roundedButton1(){
let maskPAth1 = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: [.topLeft , .topRight],
cornerRadii:CGSize(width:5.0, height:5.0))
let maskLayer1 = CAShapeLayer()
// maskLayer1.frame = self.bounds
maskLayer1.path = maskPAth1.cgPath
self.layer.mask = maskLayer1
}
func roundedButton2(){
let maskPAth1 = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: [.bottomLeft , .bottomRight],
cornerRadii:CGSize(width:5.0, height:5.0))
let maskLayer1 = CAShapeLayer()
maskLayer1.frame = self.bounds
maskLayer1.path = maskPAth1.cgPath
self.layer.mask = maskLayer1
}
}
But the width of my 1st and 2nd button is getting disturbed.
If I use button2.layer.cornerRadius = 5 then the width becomes alright. I have searched which code it altering its width but didn't find anything appropriate. And this is the only working solution i found for UIButton.Can anyone tell me why the width of button is changing and how to fix it?
The constraints of button are as follows:
Instead of using UIButton you can achieve this result by using UIView & UITapGestureRecognizer
add this extension
extension UIView {
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 create a custom view class
class CustomView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
self.roundCorners([.topRight, .topLeft], radius: 5) // you can use .topRight , .topLeft , .bottomRight, .bottomLeft
}
}
then use the class on your view
Hope this will help you

Smooth only top angles and imageview in Swift

I'm trying to round only the top top corners (TopLeft and TopRight) of a view and imageview. With the code below I was able to round up only the left corner (although there is also specified TopRight). Maybe he doesn't see the constraint placed on the storyboard.
How do I fix?
Thank you.
let rectShape = CAShapeLayer()
rectShape.bounds = self.CopertinaImage1.frame
rectShape.position = self.CopertinaImage1.center
rectShape.path = UIBezierPath(roundedRect: self.CopertinaImage1.bounds, byRoundingCorners: [.TopRight, .TopLeft], cornerRadii: CGSize(width: 10, height: 10)).CGPath
self.CopertinaImage1.layer.backgroundColor = UIColor.greenColor().CGColor
self.CopertinaImage1.layer.mask = rectShape
The right corner is always the same:
Edit:
I've tried placing the following code in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let rectShape = CAShapeLayer()
rectShape.bounds = self.CopertinaImage1.frame
rectShape.position = self.CopertinaImage1.center
rectShape.path = UIBezierPath(roundedRect: self.CopertinaImage1.bounds, byRoundingCorners: [.TopRight, .TopLeft], cornerRadii: CGSize(width: 10, height: 10)).CGPath
self.CopertinaImage1.layer.backgroundColor = UIColor.greenColor().CGColor
self.CopertinaImage1.layer.mask = rectShape
}
But it didn't work.
I've tried the code you've posted. For whatever reason when I enter your constraints and recreate your view hierarch, the auto layout engine does not call viewDidLayoutSubviews after it does it's second pass. I could force it to but calling self.view.layoutIfNeeded() from viewWillAppear. For example:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let rectShape = CAShapeLayer()
rectShape.frame = self.imageView.bounds
rectShape.path = UIBezierPath(roundedRect: self.imageView.bounds, byRoundingCorners: [.TopRight, .TopLeft], cornerRadii: CGSize(width: 10, height: 10)).CGPath
self.imageView.layer.backgroundColor = UIColor.greenColor().CGColor
self.imageView.layer.mask = rectShape
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.view.layoutIfNeeded()
}
Results in:
With both corners rounded.
I would say a better, more elegant solution that avoids this auto layout glitch is to just set the corner radius of the image view container, as it seems like that what you're doing anyways. For example:
override func viewDidLoad() {
super.viewDidLoad()
self.imageView.clipsToBounds = true
self.imageContainerView.layer.cornerRadius = 10.0
self.imageContainerView.layer.masksToBounds = true
}
You can also do this from viewDidLoad because it doesn't depend on any of your views' frames being set.
I just tried your code in a playground. Worked fine for me. Maybe I am misunderstanding the problem.

Resources