UIView with shadow, rounded only top corners and masked bottom edge - ios

I'm struggling to achieve the view that would have rounded only top corners and shadow around it. The whole view should be masked at the bottom by the superview to prevent the shadow to overlap following subview (shadow offset should be (0, 0)).
When I use:
// Adding shadow
let contentSubviewLayer = contentSubview.layer
contentSubviewLayer.masksToBounds = false
contentSubviewLayer.shadowOpacity = 0.3
contentSubviewLayer.shadowRadius = 2.5
contentSubviewLayer.shadowOffset = CGSizeMake(0, 0)
contentSubviewLayer.cornerRadius = 5.0
contentSubviewLayer.shadowPath = UIBezierPath(rect: contentSubview.bounds).CGPath
self.layer.masksToBounds = true
I get:
But after adding masking the bottom edge:
// Adding maskView to round only top corners
let maskLayer = CAShapeLayer()
maskLayer.frame = self.contentSubview.bounds
let roundedPath = UIBezierPath(roundedRect: maskLayer.bounds, byRoundingCorners: [UIRectCorner.TopLeft, UIRectCorner.TopRight], cornerRadii: CGSizeMake(CORNER_RADIUS, CORNER_RADIUS))
maskLayer.fillColor = UIColor.whiteColor().CGColor
maskLayer.backgroundColor = UIColor.clearColor().CGColor
maskLayer.path = roundedPath.CGPath
contentSubview.layer.mask = maskLayer
I'm getting:
The desired effect should be the conjunction of the two above and should resemble something like this with shadows around the left, top and right edges:
Please help!
Thank you in advance.

I tend to use an app called PaintCode to generate the Swift code I need for this type of thing. I did something similar recently and it spat this out for me:
let context = UIGraphicsGetCurrentContext()
//// Color Declarations
let chiesi_light_grey = UIColor(red: 0.851, green: 0.851, blue: 0.851, alpha: 1.000)
//// Shadow Declarations
let chiesi_shadow = NSShadow()
chiesi_shadow.shadowColor = chiesi_light_grey.colorWithAlphaComponent(0.8 * CGColorGetAlpha(chiesi_light_grey.CGColor))
chiesi_shadow.shadowOffset = CGSize(width: 0.1, height: 4.1)
chiesi_shadow.shadowBlurRadius = 2
//// Rectangle Drawing
let rectanglePath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 50, height: 54), byRoundingCorners: [UIRectCorner.BottomLeft, UIRectCorner.BottomRight], cornerRadii: CGSize(width: 8, height: 8))
rectanglePath.closePath()
CGContextSaveGState(context)
CGContextSetShadowWithColor(context, chiesi_shadow.shadowOffset, chiesi_shadow.shadowBlurRadius, (chiesi_shadow.shadowColor as! UIColor).CGColor)
UIColor.whiteColor().setFill()
rectanglePath.fill()
CGContextRestoreGState(context)
chiesi_light_grey.setStroke()
rectanglePath.lineWidth = 2
rectanglePath.stroke()
Maybe it will help you.

Related

Swift UI and shadow colors

So, I need a button in iOS / swift, that has outer shadow on bottom and right border, and inner shadow on top and left border. I was studying tutorials for several days, and came up with following solution that is ALMOST what I need.
internal func Setup() {
self.backgroundColor = .white
self.contentEdgeInsets = UIEdgeInsets(top: 10, left:30, bottom: 10, right: 30)
// Outer shadow
self.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1).cgColor
self.layer.shadowOffset = CGSize(width: 10, height: 10)
self.layer.shadowOpacity = 1.0
self.layer.shadowRadius = 6
self.layer.masksToBounds = false
self.layer.cornerRadius = self.frame.height / 2
// call to add inner shadow
self.addInnerShadow()
}
internal func addInnerShadow() {
let cornerRadius = self.bounds.height / 2
let innerShadow = CALayer()
innerShadow.frame = bounds
// Shadow path (1pt ring around bounds)
let path = UIBezierPath(roundedRect: innerShadow.bounds.insetBy(dx: -1, dy: -1), cornerRadius: cornerRadius)
let cutout = UIBezierPath(roundedRect: innerShadow.bounds, cornerRadius: cornerRadius).reversing()
path.append(cutout)
innerShadow.shadowPath = path.cgPath
innerShadow.masksToBounds = true
// Shadow properties. Color is SAME as in outer shadow, but will come out differently
innerShadow.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1).cgColor
innerShadow.shadowOffset = CGSize(width: 8, height: 8)
innerShadow.shadowOpacity = 1.0
innerShadow.shadowRadius = 6
innerShadow.cornerRadius = cornerRadius
// Add
layer.addSublayer(innerShadow)
}
Function Setup() is called during button's init(), and it all creates nice shadowed button with rounded corners.
But the question is - why the top inner shadow is so light? It should be of the same color as the bottom outer shadow. If I set any other color (like red), it is always very light, with only a tint of the required color. Why? Is it possible to control the color of inner shadow as directly as I do it with outer shadow?

Issue with rounded corners of ImageView with gradient colour

I created a rectangle using following code and now I need to rounded the corners of this rectangle. I set layer.cornerRadius also, can anyone help me?
My code as below,
private func setGradientBorder(_ ivUser:UIImageView) {
ivUser.layer.masksToBounds = true
ivUser.layer.cornerRadius = ivUser.frame.width / 2
let gradient = CAGradientLayer()
gradient.frame = CGRect(origin: CGPoint.zero, size: ivUser.frame.size)
gradient.colors = [UIColor.blue.cgColor, UIColor.green.cgColor]
let maskPath = UIBezierPath(roundedRect: ivUser.bounds,
byRoundingCorners: [.allCorners],
cornerRadii: CGSize(width: ivUser.frame.width/2, height: ivUser.frame.height/2)).cgPath
let shape = CAShapeLayer()
shape.lineWidth = 2
shape.path = maskPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradient.mask = shape
ivUser.layer.addSublayer(gradient)
}
output: gradient does not display properly rounded
This is how UIBezierPath works. Half of the line width will go on one side of the actual path, and half will go on another. Thats why it looks kinda cut out.
You will need to inset your paths rect by half of the line width, something like this:
let lineWidth: CGFloat = 2
let maskPath = UIBezierPath(roundedRect: ivUser.bounds.insetBy(dx: lineWidth/2, dy: lineWidth/2),
byRoundingCorners: [.allCorners],
cornerRadii: CGSize(width: ivUser.frame.width/2, height: ivUser.frame.height/2)).cgPath

Round left border of a UIButton

To round the bottom left corner of a UIButton, I took code from this answer and modified the line: borderLayer.strokeColor = GenerateShape.UIColorFromHex(0x989898, alpha: (1.0-0.3)).CGColor b/c it didn't work.
However, the left border of the UIButton is not rounded. How to fix?
Code
button.frame = CGRect(x: 0, y: 0, width: 0.5*popUpView.frame.width, height: 0.25*popUpView.frame.height)
button.layer.borderWidth = 3
button.roundBtnCorners(roundedCorners, radius: cornerRadius)
extension UIButton{
// Round specified corners of button
func roundBtnCorners(_ corners:UIRectCorner, radius: CGFloat)
{
let borderLayer = CAShapeLayer()
borderLayer.frame = self.layer.bounds
borderLayer.strokeColor = UIColor(red: 168/266, green: 20/255, blue: 20/255, alpha: 0.7).cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.lineWidth = 1.0
let path = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius))
borderLayer.path = path.cgPath
self.layer.addSublayer(borderLayer);
}
}
Istead of:
self.layer.addSublayer(borderLayer)
Try:
self.layer.mask = borderLayer
I cannot see how you are creating the parameters for roundBtnCorners but below is the line I changed in your code and it worked. I would check to make sure you are passing the correct parameters.
button.roundBtnCorners(.bottomLeft , radius: 10)
Solved it! It was because my button.layer.borderWidth = 3 was drawing a really thick rectangle, so I couldn't see the border drawn by roundBtnCorners
I deleted the line below:
button.layer.borderWidth = 3
Result:

CAShapeLayer cutout in UIView

My goal is to create a pretty opaque background with a clear rounded rect inset
let shape = CGRect(x: 0, y: 0, width: 200, height: 200)
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: shape, cornerRadius: 16).cgPath
maskLayer.fillColor = UIColor.clear.cgColor
maskLayer.fillRule = kCAFillRuleEvenOdd
let background = UIView()
background.backgroundColor = UIColor.black.withAlphaComponent(0.8)
view.addSubview(background)
constrain(background) {
$0.edges == $0.superview!.edges
}
background.layer.addSublayer(maskLayer)
// background.layer.mask = maskLayer
When I uncomment background.layer.mask = maskLayer, the view is totally clear. When I have it commented out, I see the semi-opaque background color but no mask cutout
Any idea what I'm doing wrong here? Thanks!
Here is a playground that I think implements your intended effect (minus .edges and constrain - that appear to be your own extensions).
a) used a red background instead of black with alpha (just to make the effect stand out)
b) set maskLayer.backgroundColor instead of maskLayer.fillColor
Adding the maskLayer as another layer to uiview is superfluous (as indicated by #Ken) but seems to do no harm.
import UIKit
import PlaygroundSupport
let bounds = CGRect(x: 0, y: 0, width: 400, height: 200)
let uiview = UIView(frame: bounds)
uiview.backgroundColor = UIColor.red.withAlphaComponent(0.8)
PlaygroundPage.current.liveView = uiview
let shape = CGRect(x: 100, y: 50, width: 200, height: 100)
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: shape, cornerRadius: 16).cgPath
maskLayer.backgroundColor = UIColor.clear.cgColor
maskLayer.fillRule = kCAFillRuleEvenOdd
uiview.layer.mask = maskLayer
image of clear inset
On the other hand, if you intended a picture frame / colored-envelope-with-transparent-window effect like below, then see gist
image of picture frame
Your mask Layer has no size.
Add this line maskLayer.frame = shape
and you don't need background.layer.addSublayer(maskLayer)

Add a shadow with CAShapeLayer?

I have a CAShapeLayer with bottom rounded corners only. What I want is to add a shadow at the bottom, but it's not working, even though the code looks obviously right. All it does it round the bottom corners but I don't see a shadow.
let shapeLayer = CAShapeLayer()
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 20, height: 20)).cgPath
shapeLayer.path = path
shapeLayer.shadowColor = UIColor(r: 233, g: 233, b: 233).cgColor
shapeLayer.shadowOffset = CGSize(width: 0.0, height: 2.8)
shapeLayer.shadowOpacity = 1.0
shapeLayer.shadowRadius = 0.0
shapeLayer.shouldRasterize = true
shapeLayer.rasterizationScale = UIScreen.main.scale
layer.rasterizationScale = UIScreen.main.scale
layer.mask = shapeLayer
The shapeLayer is rounding the corners of your view because it's set as the layer's mask. You probably want to add it as a sublayer instead.
Old:
layer.mask = shapeLayer
New:
layer.addSublayer(shapeLayer)
I wrote another answer about adding a shadow to a view with rounded corners here if it helps: https://stackoverflow.com/a/41475658/6658553
Another solution,
The following UIView extension will work with any shape you mask the view with
extension UIView {
func addShadow() {
self.backgroundColor = UIColor.clear
let roundedShapeLayer = CAShapeLayer()
let roundedMaskPath = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: [.topLeft, .bottomLeft, .bottomRight],
cornerRadii: CGSize(width: 8, height: 8))
roundedShapeLayer.frame = self.bounds
roundedShapeLayer.fillColor = UIColor.white.cgColor
roundedShapeLayer.path = roundedMaskPath.cgPath
self.layer.insertSublayer(roundedShapeLayer, at: 0)
self.layer.shadowOpacity = 0.4
self.layer.shadowOffset = CGSize(width: -0.1, height: 4)
self.layer.shadowRadius = 3
self.layer.shadowColor = UIColor.lightGray.cgColor
}
}

Resources