Change TabBar mask color (non-transparent) - ios

I am creating a TabBar with top left and top right corners rounded.
I'm using a layer mask to achieve this and it works fine, however I need the mask color to be white (its transparent showing the VC background color with the below code).
Is it possible to set the mask background color white with below approach?
I've tried setting layer and layer.mask background colours but with no success (I can't change the VC background color).
current code:
self.tabBar.layer.masksToBounds = true
self.tabBar.isTranslucent = true
self.tabBar.barStyle = .default
self.tabBar.layer.cornerRadius = 28
self.tabBar.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
Thanks.

If you want to set background color to layer mask, you need another layer
Is this the effect you needed?
You may try this:
extension UITabBar {
func roundCorners(corners: UIRectCorner, backgroundColor: UIColor, cornerColor: UIColor, radius: Int = 20) {
self.backgroundColor = cornerColor
let parentLayer = CALayer()
parentLayer.frame = bounds
parentLayer.backgroundColor = backgroundColor.cgColor
layer.insertSublayer(parentLayer, at: 0)
let maskPath = UIBezierPath(roundedRect: bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.frame = bounds
mask.path = maskPath.cgPath
parentLayer.mask = mask
}
}

Related

How do I make everything outside of a CAShapeLayer black with an opacity of 50% with Swift?

I have the following code which draws a shape:
let screenSize: CGRect = UIScreen.main.bounds
let cardLayer = CAShapeLayer()
let cardWidth = 350.0
let cardHeight = 225.0
let cardXlocation = (Double(screenSize.width) - cardWidth) / 2
let cardYlocation = (Double(screenSize.height) / 2) - (cardHeight / 2) - (Double(screenSize.height) * 0.05)
cardLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: cardWidth, height: 225.0), cornerRadius: 10.0).cgPath
cardLayer.position = CGPoint(x: cardXlocation, y: cardYlocation)
cardLayer.strokeColor = UIColor.white.cgColor
cardLayer.fillColor = UIColor.clear.cgColor
cardLayer.lineWidth = 4.0
self.previewLayer.insertSublayer(cardLayer, above: self.previewLayer)
I want everything outside of the shape to be black with an opacity of 50%. That way you can see the camera view still behind it, but it's dimmed, except where then shape is.
I tried adding a mask to previewLayer.mask but that didn't give me the effect I was looking for.
Your impulse to use a mask is correct, but let's think about what needs to be masked. You are doing three things:
Dimming the whole thing. Let's call that the dimming layer. It needs a dark semi-transparent background.
Drawing the white rounded rect. That's the shape layer.
Making a hole in the entire thing. That's the mask.
Now, the first two layers can be the same layer. That leaves only the mask. This is not trivial to construct: a mask affects its owner in terms entirely of its transparency, so we need a mask that is opaque except for an area shaped like the shape of the shape layer, which needs to be clear. To get that, we start with the shape and clip to that shape as we fill the mask — or we can clip to that shape as we erase the mask, which is the approach I prefer.
In addition, your code has some major flaws, the most important of which is that your shape layer has no size. Without a size, there is nothing to mask.
So here, with corrections and additions, is your code; I made this the entirety of a view controller, for testing purposes, and what I'm covering is the entire view controller's view rather than a particular subview or sublayer:
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .red
}
private var didInitialLayout = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if didInitialLayout {
return
}
didInitialLayout = true
let screenSize = UIScreen.main.bounds
let cardLayer = CAShapeLayer()
cardLayer.frame = self.view.bounds
self.view.layer.addSublayer(cardLayer)
let cardWidth = 350.0 as CGFloat
let cardHeight = 225.0 as CGFloat
let cardXlocation = (screenSize.width - cardWidth) / 2
let cardYlocation = (screenSize.height / 2) - (cardHeight / 2) - (screenSize.height * 0.05)
let path = UIBezierPath(roundedRect: CGRect(
x: cardXlocation, y: cardYlocation, width: cardWidth, height: cardHeight),
cornerRadius: 10.0)
cardLayer.path = path.cgPath
cardLayer.strokeColor = UIColor.white.cgColor
cardLayer.lineWidth = 8.0
cardLayer.backgroundColor = UIColor.black.withAlphaComponent(0.5).cgColor
let mask = CALayer()
mask.frame = cardLayer.bounds
cardLayer.mask = mask
let r = UIGraphicsImageRenderer(size: mask.bounds.size)
let im = r.image { ctx in
UIColor.black.setFill()
ctx.fill(mask.bounds)
path.addClip()
ctx.cgContext.clear(mask.bounds)
}
mask.contents = im.cgImage
}
And here's what we get. I didn't have a preview layer but the background is red, and as you see, the red shows through inside the white shape, which is just the effect you are looking for.
The shape layer can only affect what it covers, not the space it doesn't cover. Make a path that covers the entire video and has a hole in it where the card should be.
let areaToDarken = previewLayer.bounds // assumes origin at 0, 0
let areaToLeaveClear = areaToDarken.insetBy(dx: 50, dy: 200)
let shapeLayer = CAShapeLayer()
let path = CGPathCreateMutable()
path.addRect(areaToDarken, ...)
path.addRoundedRect(areaToLeaveClear, ...)
cardLayer.frame = previewLayer.bounds // use frame if shapeLayer is sibling
cardLayer.path = path
cardLayer.fillRule = .evenOdd // allow holes
cardLayer.fillColor = black, 50% opacity

How to set specific corner and shadow to UIView

I am trying to set top left and top right corners to tab bar along with shadow.
I am using below function which is in my UIView extension:
func addShadowWithCurve(usingCorners corners : UIRectCorner,cornerRadii : CGSize, shadowColor:UIColor,shadowOpacity:Float,shadowRadius:CGFloat,shadowOffset:CGSize){
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: cornerRadii)
layer.masksToBounds = false
let frameLayer = CAShapeLayer()
frameLayer.path = path.cgPath
frameLayer.shadowPath = path.cgPath
frameLayer.lineWidth = 1
frameLayer.strokeColor = RRSTokens.colorGrey10.cgColor
frameLayer.fillColor = backgroundColor?.cgColor
frameLayer.shadowOffset = shadowOffset
frameLayer.shadowOpacity = shadowOpacity
frameLayer.shadowRadius = shadowRadius
frameLayer.shadowColor = shadowColor.cgColor
layer.mask = frameLayer
layer.insertSublayer(frameLayer, at: 0)
}
But the result is not expected, there is a black border line visible at the top of tab bar. I have tried multiple attempts to play around layer properties to remove that black line but no luck. This is what it looks like:
Your code is perfect just add below line to your code:
self.tabBar.barStyle = .blackOpaque
Hope it'll solve your problem. Thank you.

Shadows masks to bounds only on the left and upper side. What causes this?

I'm trying to achieve a feature that allows adding a shadow to a transparent button. For this, I'm creating a layer that masks the shadows inside the view. However, my shadows are clipped on the left and upper sides but not clipped on the right and lower sides.
Here is how it looks (This is not a transparent button but they're also working fine except the shadow being clipped like this.):
And here is my code for achieving this:
private func applyShadow() {
layer.masksToBounds = false
if shouldApplyShadow && shadowLayer == nil {
shadowLayer = CAShapeLayer()
let shapePath = CGPath(roundedRect: bounds, cornerWidth: cornerRadi, cornerHeight: cornerRadi, transform: nil)
shadowLayer.path = shapePath
shadowLayer.fillColor = backgroundColor?.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowRadius = shadowRadius ?? 8
shadowLayer.shadowColor = (shadowColor ?? .black).cgColor
shadowLayer.shadowOffset = shadowOffset ?? CGSize(width: 0, height: 0)
shadowLayer.shadowOpacity = shadowOpacity ?? 0.8
layer.insertSublayer(shadowLayer!, at: 0)
/// If there's background color, there is no need to mask inner shadows.
if backgroundColor != .none && !(innerShadows ?? false) {
let maskLayer = CAShapeLayer()
maskLayer.path = { () -> UIBezierPath in
let path = UIBezierPath()
path.append(UIBezierPath(cgPath: shapePath))
path.append(UIBezierPath(rect: UIScreen.main.bounds))
path.usesEvenOddFillRule = true
return path
}().cgPath
maskLayer.fillRule = .evenOdd
shadowLayer.mask = maskLayer
}
}
}
I think that's something related to the Even-Odd Fill Rule algorithm, I'm not sure. But how can I overcome this clipping problem?
Thanks in advance.
EDIT:
This is what a transparent button with borders and text on it looks when shadow applied.. Which I don't want.
What it should look like this. No shadows inside but also a clear background color. (Except the clipped top and left sides):
I think a couple issues...
You are appending UIBezierPath(rect: UIScreen.main.bounds) to your path, but that puts the top-left corner at the top-left corner of the layer... which "clips" the top-left shadow.
If you DO have a background color, you'll need to clip those corners as well, or they will "bleed" outside the rounded corners.
Give this a try (only slightly modified). It's designated #IBDesignable so you can see how it looks in Storyboard / Interface Builder (I did not set any of the properties to inspectable -- I'll leave that up to you if you want to do so):
#IBDesignable
class MyRSButton: UIButton {
var shouldApplyShadow: Bool = true
var innerShadows: Bool?
var cornerRadi: CGFloat = 8.0
var shadowLayer: CAShapeLayer!
var shadowRadius: CGFloat?
var shadowColor: UIColor?
var shadowOffset: CGSize?
var shadowOpacity: Float?
override func layoutSubviews() {
super.layoutSubviews()
applyShadow()
}
private func applyShadow() {
// needed to prevent background color from bleeding past
// the rounded corners
cornerRadi = bounds.size.height * 0.5
layer.cornerRadius = cornerRadi
layer.masksToBounds = false
if shouldApplyShadow && shadowLayer == nil {
shadowLayer = CAShapeLayer()
let shapePath = CGPath(roundedRect: bounds, cornerWidth: cornerRadi, cornerHeight: cornerRadi, transform: nil)
shadowLayer.path = shapePath
shadowLayer.fillColor = backgroundColor?.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowRadius = shadowRadius ?? 8
shadowLayer.shadowColor = (shadowColor ?? .black).cgColor
shadowLayer.shadowOffset = shadowOffset ?? CGSize(width: 0, height: 0)
shadowLayer.shadowOpacity = shadowOpacity ?? 0.8
layer.insertSublayer(shadowLayer!, at: 0)
/// If there's background color, there is no need to mask inner shadows.
if backgroundColor != .none && !(innerShadows ?? false) {
let maskLayer = CAShapeLayer()
maskLayer.path = { () -> UIBezierPath in
let path = UIBezierPath()
path.append(UIBezierPath(cgPath: shapePath))
// define a rect that is 80-pts wider and taller
// than the button... this will "expand" it from center
let r = bounds.insetBy(dx: -40, dy: -40)
path.append(UIBezierPath(rect: r))
path.usesEvenOddFillRule = true
return path
}().cgPath
maskLayer.fillRule = .evenOdd
shadowLayer.mask = maskLayer
}
}
}
}
Result:

How to cut portion of CALayer in iOS Swift? [duplicate]

This question already has answers here:
How can I 'cut' a transparent hole in a UIImage?
(4 answers)
Closed 3 years ago.
I am trying to create a QR Reader. For that I am showing a rectOfInterest with some CALayer for visual representation. I want to show a box with some border at the corners and black background with some opacity so hide the other view from the AVCaptureVideoPreviewLayer. What I have achieved till now looks like this:
As you can see the CALayer is there but I want to cut the box portion of the layer so that that blackish thing does not come there. The code I am using to do this is like below:
func createTransparentLayer()->CALayer{
let shape = CALayer()
shape.frame = self.scanView.layer.bounds
shape.backgroundColor = UIColor.black.cgColor
shape.opacity = 0.7
return shape
}
I looked into other questions for this, seems like you have the mask the layer with the cut portion. So I subclassed the CALayer and cleared the context in drawInContext and set the mask property of the super layer to this. After that I get nothing. Everything is invisible there. What is wrong in this?
The code I tried is this:
class TransparentLayer: CALayer {
override func draw(in ctx: CGContext) {
self.backgroundColor = UIColor.black.cgColor
self.opacity = 0.7
self.isOpaque = true
ctx.clear(CGRect(x: superlayer!.frame.size.width / 2 - 100, y: superlayer!.frame.size.height / 2 - 100, width: 200, height: 200))
}
}
then set the mask property like this:
override func viewDidLayoutSubviews() {
self.rectOfInterest = CGRect(x: self.scanView.layer.frame.size.width / 2 - 100, y: self.scanView.layer.frame.size.height / 2 - 100, width: 200, height: 200)
scanView.rectOfInterest = self.rectOfInterest
let shapeLayer = self.createFrame()
scanView.doInitialSetup()
self.scanView.layer.mask = self.createTransparentLayer()
self.scanView.layer.addSublayer(shapeLayer)
}
here the shapeLayer is the bordered corner in the screenshot. How can I achieve this?
I have added view in my view controller which is centre align vertically and horizontally. Also fixed height and width to 200. Then created extension of UIView and added following code:
extension UIView {
func strokeBorder() {
self.backgroundColor = .clear
self.clipsToBounds = true
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = UIBezierPath(rect: self.bounds).cgPath
self.layer.mask = maskLayer
let line = NSNumber(value: Float(self.bounds.width / 2))
let borderLayer = CAShapeLayer()
borderLayer.path = maskLayer.path
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor.white.cgColor
borderLayer.lineDashPattern = [line]
borderLayer.lineDashPhase = self.bounds.width / 4
borderLayer.lineWidth = 10
borderLayer.frame = self.bounds
self.layer.addSublayer(borderLayer)
}
}
Use:
By using outlet of view and call method to set border as you have request.
self.scanView.strokeBorder()
To clear the backgroundColor respective to mask view, I have added following code to clear it.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.scanView.strokeBorder()
self.backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
// Draw a graphics with a mostly solid alpha channel
// and a square of "clear" alpha in there.
UIGraphicsBeginImageContext(self.backgroundView.bounds.size)
let cgContext = UIGraphicsGetCurrentContext()
cgContext?.setFillColor(UIColor.white.cgColor)
cgContext?.fill(self.backgroundView.bounds)
cgContext?.clear(CGRect(x:self.scanView.frame.origin.x, y:self.scanView.frame.origin.y, width: self.scanView.frame.width, height: self.scanView.frame.height))
let maskImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// Set the content of the mask view so that it uses our
// alpha channel image
let maskView = UIView(frame: self.backgroundView.bounds)
maskView.layer.contents = maskImage?.cgImage
self.backgroundView.mask = maskView
}
Output:
I'm not using camera in background.

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.

Resources