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
Related
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
Is there a way to apply rounded corners to certain corners only, and at the same time apply a shadow layer?
I tried doing that using the following extension, but that didn't work for me:
extension UIView {
func roundCorners(_ corners: UIRectCorner, radius: CGFloat, shadow: Bool = false) {
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
if shadow {
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 2)
self.layer.shadowOpacity = 0.3
self.layer.shadowRadius = 3.0
}
}
}
So, I tossed in some code into playground and had a quick playground, based on the example from How to round only specific corners using maskedCorners
let view = UIView(frame: CGRect(x: 100, y: 100, width: 500, height: 500))
view.backgroundColor = UIColor.white
view.layer.cornerRadius = 15
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMaxYCorner]
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 0)
view.layer.shadowOpacity = 1.0
view.layer.shadowRadius = 4
//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
I did some testing:
With no corner radius
With masked corner radius
With reduced shawdowRadius
And just to be sure, increased corner radius
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
}
}
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
I'm making a custom view that i want to mask and to add shadow to it.
the masking:
let p = UIBezierPath()
p.moveToPoint(CGPointMake(20, 20))
p.addLineToPoint(CGPointMake(100, 20))
p.addLineToPoint(CGPointMake(100, 50))
p.addLineToPoint(CGPointMake(110, 55))
p.addLineToPoint(CGPointMake(100, 60))
p.addLineToPoint(CGPointMake(100, 100))
p.addLineToPoint(CGPointMake(20, 100))
p.closePath()
let s = CAShapeLayer()
s.frame = layer.bounds
s.path = p.CGPath
s.fillColor = UIColor.greenColor().CGColor
layer.mask = s
the masking works, now i want to add a shadow.
but its not working.
i tried to add shadow to the main layer and nothing happens.
layer.shadowColor = UIColor.yellowColor().CGColor
layer.shadowRadius = 10
layer.shadowOpacity = 0.9
layer.shadowOffset = CGSizeZero
i tried to add it to the mask layer and i got the main view masked with a shadow.
s.shadowColor = UIColor.yellowColor().CGColor
s.shadowRadius = 10
s.shadowOpacity = 0.9
s.shadowOffset = CGSizeZero
Any suggestions how to add this yellow shadow to the masked view?
Thanks
Thanks #WilsonXJ
I changed mask to addSubLayer.
This is the answer that worked for me:
let p = UIBezierPath()
p.moveToPoint(CGPointMake(20, 20))
p.addLineToPoint(CGPointMake(100, 20))
p.addLineToPoint(CGPointMake(100, 50))
p.addLineToPoint(CGPointMake(110, 55))
p.addLineToPoint(CGPointMake(100, 60))
p.addLineToPoint(CGPointMake(100, 100))
p.addLineToPoint(CGPointMake(20, 100))
p.closePath()
let s = CAShapeLayer()
s.fillColor = UIColor.whiteColor().CGColor
s.frame = layer.bounds
s.path = p.CGPath
layer.backgroundColor = UIColor.clearColor().CGColor
layer.addSublayer(s)
layer.masksToBounds = true
layer.shadowColor = UIColor.yellowColor().CGColor
layer.shadowOffset = CGSizeZero
layer.shadowOpacity = 0.9
layer.shadowPath = p.CGPath
layer.shadowRadius = 10
I don't think that current answer is the right one, because there is no layer.mask usage anymore.
In case when you need to use layer.mask and to drop shadow of masked layer - the obvious solution is to add another layer below masked layer that will have same shape as layer.mask and drop it's shadow
example:
let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 500, height: 500)))
view.backgroundColor = .white
PlaygroundPage.current.liveView = view
let path: CGPath = ...
let maskedView = UIView(frame: path.boundingBox)
maskedView.center = view.center
maskedView.backgroundColor = .green
view.addSubview(maskedView)
let maskLayer = CAShapeLayer()
maskLayer.frame = maskedView.bounds
maskLayer.path = path
maskedView.layer.mask = maskLayer
let shadowLayer = CAShapeLayer()
shadowLayer.path = path
shadowLayer.frame = maskedView.frame
shadowLayer.shadowOpacity = 0.4
shadowLayer.shadowRadius = 2
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowOffset = CGSize(width: 4, height: 4)
maskedView.superview!.layer.insertSublayer(shadowLayer, below: maskedView.layer)