How to round a shadow in iOS? - ios

I've created a rounded UIView, which should also have a shadow. So far, everything works, but on the corners, the shadow isn't rounded properly.
How to round the shadow corner?
Here's the code and a screenshot:
popupView.layer.cornerRadius = 15
popupView.layer.shadowColor = UIColor.black.cgColor
popupView.layer.shadowOffset = CGSize(width: 0, height: 0)
popupView.layer.shadowOpacity = 0.3
popupView.layer.shadowRadius = 3.0
popupView.layer.shadowPath = UIBezierPath(rect: popupView.bounds).cgPath
popupView.layer.shouldRasterize = false

So, I basically threw your code into Playground and played around with a bit, what I found was, I could basically remove view.layer.shadowPath and it would work...
import UIKit
let view = UIView(frame: CGRect(x: 100, y: 100, width: 500, height: 500))
view.backgroundColor = UIColor.white
view.layer.cornerRadius = 15
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 0)
view.layer.shadowOpacity = 1.0
view.layer.shadowRadius = 6.0
//view.layer.shadowPath = UIBezierPath(rect: view.bounds).cgPath
view.layer.shouldRasterize = false
let outter = UIView(frame: CGRect(x: 0, y: 0, width: 700, height: 700))
outter.backgroundColor = UIColor.red
outter.addSubview(view)
outter
What I also found was:
view.layer.masksToBounds = true did not achieve the desired result
view.layer.shadowPath = UIBezierPath(roundedRect: view.bounds, cornerRadius: 15).cgPath also works, but adds in an additional point of failure, as you need to remember to set the cornerRadius property if you change it on the view, or add a variable to seed both, but since if you didn't set the shadowPath, it would generate the same result, it makes me wonder why you'd both using it.

Use
popupView.layer.shadowPath = UIBezierPath(roundedRect: popupView.bounds, cornerRadius: 15).cgPath
instead of
popupView.layer.shadowPath = UIBezierPath(rect: popupView.bounds).cgPath
and add a little bit of shadowOpacity like a 1.3 to see better the shadow.

You need to set the views layers masksToBounds value to true
popupView.layer.masksToBounds = true

Related

Rounded corners and shadow for navigation bar

I want to create something like this:
Output
This is what I've tried so far:
class RoundedShadowCorners {
func shadowTopBar(_ topBar: UINavigationBar,_ offset: CGFloat,_ navigationItem: UINavigationItem){
topBar.isTranslucent = false
topBar.tintColor = UIColor.orange
topBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
topBar.shadowImage = UIImage()
topBar.backgroundColor = UIColor.white
let shadowView = UIView(frame: CGRect(x: 0, y: -offset, width: (topBar.bounds.width), height: (topBar.bounds.height) + offset))
shadowView.backgroundColor = UIColor.white
topBar.insertSubview(shadowView, at: 1)
let shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: shadowView.bounds, byRoundingCorners: [.bottomLeft , .bottomRight , .topLeft], cornerRadii: CGSize(width: 20, height: 20)).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
shadowLayer.shadowOpacity = 0.8
shadowLayer.shadowRadius = 2
shadowView.layer.insertSublayer(shadowLayer, at: 0)
topBar.prefersLargeTitles = true
topBar.topItem?.title = "HJFSKDJKA"
}
}
Problem with this is that this makes the title text be behind the actual NavigationBar and I can only make it come forward if I create a new UIView for the title and try positioning it on the screen, which makes it extremely difficult to be responsive.
offset isview.safeAreaInsets.top
I would prefer to do it without making a new UIView, because it makes things complicated, but I couldn't even begin to do it.
This is updated & tested code, there were certain lines which needs to be updated to overcome this behaviour,go through my in-line comments for details.
func shadowTopBar(_ topBar: UINavigationBar,_ offset: CGFloat){
// Set the prefers title style first
// since this is how navigation bar bounds gets calculated
//
topBar.prefersLargeTitles = true
topBar.topItem?.title = "HJFSKDJKA"
topBar.isTranslucent = false
topBar.tintColor = UIColor.orange
topBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
topBar.shadowImage = UIImage()
topBar.backgroundColor = UIColor.white
// Make your y position to the max of the uiNavigationBar
// Height should the cornerRadius height, in your case lets say 20
let shadowView = UIView(frame: CGRect(x: 0, y: topBar.bounds.maxY, width: (topBar.bounds.width), height: 20))
// Make the backgroundColor of your wish, though I have made it .clear here
// Since we're dealing it in the shadow layer
shadowView.backgroundColor = .clear
topBar.insertSubview(shadowView, at: 1)
let shadowLayer = CAShapeLayer()
// While creating UIBezierPath, bottomLeft & right will do the work for you in this case
// I've removed the extra element from here.
shadowLayer.path = UIBezierPath(roundedRect: shadowView.bounds, byRoundingCorners: [.bottomLeft , .bottomRight], cornerRadii: CGSize(width: 20, height: 20)).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = shadowLayer.path
// This too you can set as per your desired result
shadowLayer.shadowOffset = CGSize(width: 2.0, height: 4.0)
shadowLayer.shadowOpacity = 0.8
shadowLayer.shadowRadius = 2
shadowView.layer.insertSublayer(shadowLayer, at: 0)
}
Here's a detailed article on this. Medium

Add shadow around a masked UIView

I am trying to create a pop-over which will have a tip. Following is the code for this
tipView.frame = CGRect(x: at.x - size.width, y: at.y, width: size.width, height: size.height)
let imgView = UIImageView.init(frame: CGRect(x: 0, y: 0, width: tipView.frame.width, height: tipView.frame.height))
imgView.image = #imageLiteral(resourceName: "popup.png")
tipView.mask = imgView
I masked UIView with an image which is in the shape of pop-over.
Now I want to add shadow to the UIView on all 4 sides. I tried all the methods. But the shadow is not visible
Add following code to add shadow and corner radius to tipView. Clear backgroundColor and make clipsToBounds = false of tipView.
let shapeLayer = CAShapeLayer()
shapeLayer.path = UIBezierPath(roundedRect: tipView.bounds, byRoundingCorners: [.topLeft, .bottomLeft, .bottomRight], cornerRadii: CGSize(width: 20, height: 20)).cgPath
shapeLayer.fillColor = UIColor.darkGray.cgColor
shapeLayer.masksToBounds = false
shapeLayer.shadowColor = UIColor.darkGray.cgColor
shapeLayer.shadowPath = shapeLayer.path
shapeLayer.shadowOffset = CGSize(width: 0, height: 2)
shapeLayer.shadowOpacity = 0.5
shapeLayer.shadowRadius = 2.0
tipView.layer.insertSublayer(shapeLayer, at: 0)
Add this code to your view layer:
// set the corner radius
layer.cornerRadius = 6.0
layer.masksToBounds = false
// set the shadow properties
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 1.0)
layer.shadowOpacity = 0.2
layer.shadowRadius = 4.0
Please go through the this link for details

Unable to set bottom shadow of UIView in iOS?

I want to set the bottom shadow of UIView.I am also setting corner radius.If i set maskToBounds to true then i am not able to set the shadow of UIView please tell how can i set both corner radius & shadow of UIView.
func addShadwToView(){
self.viewContainer.layer.masksToBounds = true;
self.viewContainer.layer.shadowRadius = 15;
self.viewContainer.layer.shadowOffset = CGSize(width: 0, height: 20)
self.viewContainer.layer.shadowOpacity = 0.5;
self.viewContainer.layer.cornerRadius = 5
self.viewContainer.layer.borderColor = UIColor.lightGray.cgColor
self.viewContainer.layer.borderWidth = 0.5
}
If you want both a corner radius and a drop shadow, so don't turn on -masksToBounds, but rather set the corner radius and set the bezier path of the shadow with a rounded rect. Keep the radius of the two the same:
Try it:
func addShadwToView(){
var borderLine = CAShapeLayer()
borderLine.path = UIBezierPath(roundedRect: frame2, byRoundingCorners: [.allCorners], cornerRadii: CGSize(width: 30, height: 0)).cgPath
borderLine.shadowColor = UIColor.white.cgColor
borderLine.shadowOffset = CGSize(width: 0, height: 1)
borderLine.shadowOpacity = 0.3
borderLine.shadowRadius = 10
self.viewContainer.layer.masksToBounds = true;
self.viewContainer.layer.cornerRadius = 5
self.viewContainer.layer.borderColor = UIColor.lightGray.cgColor
self.viewContainer.layer.borderWidth = 0.5
self.viewContainer.layer.addSublayer(borderLine)
}
Try this code...this will solve your problem
func addShadow(){
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: customView.frame.width, height: customView.frame.height))
customView.layer.shadowColor = UIColor.lightGray.cgColor
customView.layer.shadowOffset = CGSize(width: 0, height: 20)
customView.layer.shadowOpacity = 0.5
customView.layer.shadowRadius = 15
customView.layer.masksToBounds = false
customView.layer.shadowPath = shadowPath.cgPath
}

Shadow on UIView layer

I've make a path in order to mask my view:
let path = // create magic path (uiview bounds + 2 arcs)
let mask = CAShapeLayer()
mask.path = path.cgPath
view.layer.masksToBounds = false
view.layer.mask = mask
Up to here all ok.
Now I would like to add a shadow that follows path, is it possibile?
I try in several way, the last one is:
mask.shadowPath = path.cgPath
mask.shadowColor = UIColor.red.cgColor
mask.shadowOffset = CGSize(width: 10, height: 2.0)
mask.shadowOpacity = 0.5
But this produce a partial shadow and with color of the original view..
With debug view hierarchy:
Any advice?
Final result should be similar to this, but with shadow that "follows" arcs on path.
When you add a mask to a layer, it clips anything outside that mask - including the shadow. To achieve this you'll need to add a "shadow" view below your masked view, that has the same path as the mask.
Or add a shadow layer to the masked view's superview.
let view = UIView(frame: CGRect(x: 50, y: 70, width: 100, height: 60))
view.backgroundColor = .cyan
let mask = CAShapeLayer()
mask.path = UIBezierPath(roundedRect: view.bounds, cornerRadius: 10).cgPath
view.layer.mask = mask
let shadowLayer = CAShapeLayer()
shadowLayer.frame = view.frame
shadowLayer.path = UIBezierPath(roundedRect: view.bounds, cornerRadius: 10).cgPath
shadowLayer.shadowOpacity = 0.5
shadowLayer.shadowRadius = 5
shadowLayer.masksToBounds = false
shadowLayer.shadowOffset = .zero
let container = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
container.backgroundColor = .white
container.layer.addSublayer(shadowLayer)
container.addSubview(view)
If you're going to be using this elsewhere, you could create a ShadowMaskedView that contains the shadow layer, and the masked view - maybe with a path property.
You can try this extension:
extension UIView {
func dropShadow() {
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = 0.5
self.layer.shadowOffset = CGSize(width: -1, height: 1)
self.layer.shadowRadius = 1
self.layer.shadowPath = UIBezierPath(rect: self.bounds).cgPath
self.layer.shouldRasterize = true
}
}

Corner radius for a button to align with a view below

There are tons of answers on SO for rounding a particular corner. The issue I'm running into is I'm trying to align a button corner to a rounded corner of the view below. Please see the image. The yellow view is rounded in 4 corners. I'm trying to get the close button to round off at top right to align with the yellow view's round corner. I've used the Swift 3 code below, but the button stays square. Can anyone please point out what is missing?
viewRaised is the yellow view.
Many thanks!
let path = UIBezierPath(roundedRect: btnCancel.layer.bounds, byRoundingCorners:[.topRight, .bottomLeft], cornerRadii: CGSize(width: 10, height: 10))
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
btnCancel.layer.mask = maskLayer
btnCancel.layer.masksToBounds = true
self.viewRaised.layer.cornerRadius = 10
self.viewRaised.layer.masksToBounds = false
self.viewRaised.layer.shadowOffset = CGSize(width: 5, height: 10);
self.viewRaised.layer.shadowRadius = 10;
self.viewRaised.layer.shadowOpacity = 0.5;
UPDATE:
Interesting thing is that the same code seems to be working but only for top left. See the second image.
self.viewRaised.layer.cornerRadius = 10
self.viewRaised.layer.masksToBounds = false
self.viewRaised.layer.shadowOffset = CGSize(width: 5, height: 10);
self.viewRaised.layer.shadowRadius = 10;
self.viewRaised.layer.shadowOpacity = 0.5;
let path = UIBezierPath(roundedRect: btnCancel.layer.bounds, byRoundingCorners:[.allCorners], cornerRadii: CGSize(width: 10, height: 10))
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
btnCancel.layer.mask = maskLayer
This is quite perplexing. I'm using XCode Version 8.0 beta 6 (8S201h)
Your btnCancel.layer.mask should be set to the yellow view.
You need to add btnCancel as a sublayer of of yellow view's parent.
Example (Swift 3.x) :
let yellowView = UIView(frame: CGRectMake(20.0, 20.0, 200.0, 200.0))
yellowView.backgroundColor = UIColor.yellowColor()
yellowView.layer.borderColor = UIColor.clearColor().CGColor
yellowView.layer.borderWidth = 1.0
yellowView.layer.cornerRadius = 10.0
self.view.addSubview(yellowView) // Add yellowView to self's main view
let btnCancel = UIView(frame: CGRectMake(20.0, 20.0, 45.0, 45.0))
btnCancel.backgroundColor = UIColor.redColor()
btnCancel.layer.mask = yellowView.layer // Set btnCancel.layer.mask to yellowView.layer
self.view.addSubview(btnCancel) // Add btnCancel to self's main view, NOT yellowView
NOTE:
You don't need to enable clipsToBounds because you're setting a mask layer.
You Also don't need to create a new CAShapeLayer for the mask. Use yellowView's layer as the mask.
Swift 4.x :
let yellowView = UIView(frame: CGRect(x: 20.0, y: 20.0, width: 200.0, height: 200.0))
yellowView.backgroundColor = .yellow
yellowView.layer.borderColor = UIColor.clear.cgColor
yellowView.layer.borderWidth = 1.0
yellowView.layer.cornerRadius = 10.0
self.view.addSubview(yellowView) // Add yellowView to self's main view
let btnCancel = UIView(frame: CGRect(x: 20.0, y: 20.0, width: 45.0, height: 45.0))
btnCancel.backgroundColor = .red
btnCancel.layer.mask = yellowView.layer // Set btnCancel.layer.mask to yellowView.layer
self.view.addSubview(btnCancel) // Add btnCancel to self's main view, NOT yellowView
All I needed to do was remove the whole of this. But it worked if I remove the bolded line only. But the rest of the lines were not needed once since Cancel Button was the subview of the yellow view.
let path = UIBezierPath(roundedRect: btnCancel.layer.bounds, byRoundingCorners:[.topRight, .bottomLeft], cornerRadii: CGSize(width: 10, height: 10))
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
btnCancel.layer.mask = maskLayer
btnCancel.layer.masksToBounds = true

Resources