Extension of UIView - ios

My code is written in Swift 2.0 and with a deployment target of iOS 8.0 or later. I am trying to extend UIView so that instead of using UIView.animateWithDuration(), I can use my custom function. I want to take a UIView that's a circle and keep it so that its corner radius remains half of its height and width but, fills a square. So the UIView should expand in its current shape until it reaches a rectangular bound. I would like to use an extension on UIView. As below.
extension UIView {
func customAnimation() {
}
}
Code that works is.
let ViewFrame: CGRect = CGRect(x: 100, y: 100, width: 10, height: 10)
let View: UIView = UIView(frame: ViewFrame)
View.layer.cornerRadius = 5
View.bounds = CGRect(x: 0, y: 0, width: 300, height: 300)
View.layer.masksToBounds = true
UIView.animateWithDuration(duration: 10.0, delay: 0.0, options: .CurveEaseInOut, animation: {
View.transform = CGAffineTransformMakeScale(100.0, 100.0)
}, completion: nil)

enum AIEdge:Int {
case
Top,
Left,
Bottom,
Right,
Top_Left,
Top_Right,
Bottom_Left,
Bottom_Right,
All,
None
}
extension UIView {
//MARK:- HEIGHT / WIDTH
var width:CGFloat {
return self.frame.size.width
}
var height:CGFloat {
return self.frame.size.height
}
var xPos:CGFloat {
return self.frame.origin.x
}
var yPos:CGFloat {
return self.frame.origin.y
}
//MARK:- DASHED BORDER
func drawDashedBorderAroundView() {
let cornerRadius: CGFloat = self.frame.size.width / 2
let borderWidth: CGFloat = 0.5
let dashPattern1: Int = 4
let dashPattern2: Int = 2
let lineColor = WHITE_COLOR
//drawing
let frame: CGRect = self.bounds
let shapeLayer = CAShapeLayer()
//creating a path
let path: CGMutablePath = CGMutablePath()
//drawing a border around a view
path.move(to: CGPoint(x: CGFloat(0), y: CGFloat(frame.size.height - cornerRadius)), transform: .identity)
path.addLine(to: CGPoint(x: CGFloat(0), y: CGFloat(cornerRadius)), transform: .identity)
path.addArc(center: CGPoint(x: CGFloat(cornerRadius), y: CGFloat(cornerRadius)), radius: CGFloat(cornerRadius), startAngle: CGFloat(M_PI), endAngle: CGFloat(-M_PI_2), clockwise: false, transform: .identity)
path.addLine(to: CGPoint(x: CGFloat(frame.size.width - cornerRadius), y: CGFloat(0)), transform: .identity)
path.addArc(center: CGPoint(x: CGFloat(frame.size.width - cornerRadius), y: CGFloat(cornerRadius)), radius: CGFloat(cornerRadius), startAngle: CGFloat(-M_PI_2), endAngle: CGFloat(0), clockwise: false, transform: .identity)
path.addLine(to: CGPoint(x: CGFloat(frame.size.width), y: CGFloat(frame.size.height - cornerRadius)), transform: .identity)
path.addArc(center: CGPoint(x: CGFloat(frame.size.width - cornerRadius), y: CGFloat(frame.size.height - cornerRadius)), radius: CGFloat(cornerRadius), startAngle: CGFloat(0), endAngle: CGFloat(M_PI_2), clockwise: false, transform: .identity)
path.addLine(to: CGPoint(x: CGFloat(cornerRadius), y: CGFloat(frame.size.height)), transform: .identity)
path.addArc(center: CGPoint(x: CGFloat(cornerRadius), y: CGFloat(frame.size.height - cornerRadius)), radius: CGFloat(cornerRadius), startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI), clockwise: false, transform: .identity)
//path is set as the _shapeLayer object's path
shapeLayer.path = path
shapeLayer.backgroundColor = UIColor.clear.cgColor
shapeLayer.frame = frame
shapeLayer.masksToBounds = false
shapeLayer.setValue(NSNumber(value: false), forKey: "isCircle")
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = borderWidth
shapeLayer.lineDashPattern = [NSNumber(integerLiteral: dashPattern1),NSNumber(integerLiteral: dashPattern2)]
shapeLayer.lineCap = kCALineCapRound
self.layer.addSublayer(shapeLayer)
//self.layer.cornerRadius = cornerRadius
}
//MARK:- ROTATE
func rotate(angle: CGFloat) {
let radians = angle / 180.0 * CGFloat(M_PI)
self.transform = self.transform.rotated(by: radians);
}
//MARK:- BORDER
func applyBorderDefault() {
self.applyBorder(color: UIColor.red, width: 1.0)
}
func applyBorderDefault1() {
self.applyBorder(color: UIColor.green, width: 1.0)
}
func applyBorderDefault2() {
self.applyBorder(color: UIColor.blue, width: 1.0)
}
func applyBorder(color:UIColor, width:CGFloat) {
self.layer.borderColor = color.cgColor
self.layer.borderWidth = width
}
//MARK:- CIRCLE
func applyCircle() {
self.layer.cornerRadius = min(self.frame.size.height, self.frame.size.width) * 0.5
self.layer.masksToBounds = true
self.clipsToBounds = true
}
func applyCircleWithRadius(radius:CGFloat) {
self.layer.cornerRadius = radius
self.layer.masksToBounds = true
}
//MARK:- CORNER RADIUS
func applyCornerRadius(radius:CGFloat) {
self.layer.cornerRadius = radius
self.layer.masksToBounds = true
}
func applyCornerRadiusDefault() {
self.applyCornerRadius(radius: 5.0)
}
//MARK:- SHADOW
func applyShadowDefault() {
self.applyShadowWithColor(color: UIColor.black, opacity: 0.5, radius: 1)
}
func applyShadowWithColor(color:UIColor) {
self.applyShadowWithColor(color: color, opacity: 0.5, radius: 1)
}
func applyShadowWithColor(color:UIColor, opacity:Float, radius: CGFloat) {
self.layer.shadowColor = color.cgColor
self.layer.shadowOpacity = opacity
self.layer.shadowOffset = CGSize.zero
self.layer.shadowRadius = radius
self.clipsToBounds = false
}
func applyShadowWithColor(color:UIColor, opacity:Float, radius: CGFloat, edge:AIEdge, shadowSpace:CGFloat) {
var sizeOffset:CGSize = CGSize.zero
switch edge {
case .Top:
sizeOffset = CGSize(width: 0, height: -shadowSpace) //CGSizeMake(0, -shadowSpace)
case .Left:
sizeOffset = CGSize(width: -shadowSpace, height: 0) //CGSizeMake(-shadowSpace, 0)
case .Bottom:
sizeOffset = CGSize(width: 0, height: shadowSpace) //CGSizeMake(0, shadowSpace)
case .Right:
sizeOffset = CGSize(width: shadowSpace, height: 0) //CGSizeMake(shadowSpace, 0)
case .Top_Left:
sizeOffset = CGSize(width: -shadowSpace, height: -shadowSpace) //CGSizeMake(-shadowSpace, -shadowSpace )
case .Top_Right:
sizeOffset = CGSize(width: shadowSpace, height: -shadowSpace) //CGSizeMake(shadowSpace, -shadowSpace)
case .Bottom_Left:
sizeOffset = CGSize(width: -shadowSpace, height: shadowSpace) //CGSizeMake(-shadowSpace, shadowSpace)
case .Bottom_Right:
sizeOffset = CGSize(width: shadowSpace, height: shadowSpace) //CGSizeMake(shadowSpace, shadowSpace)
case .All:
sizeOffset = CGSize(width: 0, height: 0) //CGSizeMake(0, 0)
case .None:
sizeOffset = CGSize.zero
}
self.layer.shadowColor = color.cgColor
self.layer.shadowOpacity = opacity
self.layer.shadowOffset = sizeOffset
self.layer.shadowRadius = radius
self.clipsToBounds = false
}
func addBorderWithColor(color:UIColor, edge:AIEdge, thicknessOfBorder:CGFloat) {
// dispatch_async(dispatch_get_main_queue()) {
DispatchQueue.main.async {
var rect:CGRect = CGRect.zero
switch edge {
case .Top:
rect = CGRect(x: 0, y: 0, width: self.width, height: thicknessOfBorder) //CGRectMake(0, 0, self.width, thicknessOfBorder);
case .Left:
rect = CGRect(x: 0, y: 0, width: thicknessOfBorder, height:self.width ) //CGRectMake(0, 0, thicknessOfBorder, self.height);
case .Bottom:
rect = CGRect(x: 0, y: self.height - thicknessOfBorder, width: self.width, height: thicknessOfBorder) //CGRectMake(0, self.height - thicknessOfBorder, self.width, thicknessOfBorder);
case .Right:
rect = CGRect(x: self.width-thicknessOfBorder, y: 0, width: thicknessOfBorder, height: self.height) //CGRectMake(self.width-thicknessOfBorder, 0,thicknessOfBorder, self.height);
default:
break
}
let layerBorder = CALayer()
layerBorder.frame = rect
layerBorder.backgroundColor = color.cgColor
self.layer.addSublayer(layerBorder)
}
}
func animateVibrate() {
let duration = 0.05
UIView.animate(withDuration: duration ,
animations: {
self.transform = self.transform.translatedBy(x: 5, y: 0)
},
completion: { finish in
UIView.animate(withDuration: duration ,
animations: {
self.transform = self.transform.translatedBy(x: -10, y: 0)
},
completion: { finish in
UIView.animate(withDuration: duration ,
animations: {
self.transform = self.transform.translatedBy(x: 10, y: 0)
},
completion: { finish in
UIView.animate(withDuration: duration ,
animations: {
self.transform = self.transform.translatedBy(x: -10, y: 0)
},
completion: { finish in
UIView.animate(withDuration: duration){
self.transform = CGAffineTransform.identity
}
})
})
})
})
}
}

You really just have to move the animation logic into the extension function like this
extension UIView {
func customAnimtation() {
UIView.animateWithDuration(10.0, delay: 0.0, options: .CurveEaseInOut, animations: {
self.transform = CGAffineTransformMakeScale(100.0, 100.0)
}, completion: nil)
}
}
let ViewFrame: CGRect = CGRect(x: 100, y: 100, width: 10, height: 10)
let View: UIView = UIView(frame: ViewFrame)
View.layer.cornerRadius = 5
View.bounds = CGRect(x: 0, y: 0, width: 300, height: 300)
View.layer.masksToBounds = true
View.customAnimtation()

Related

How to join a few rectangle UIBezierPath objects into one?

I simply do the following in code:
let path = UIBezierPath(rect: blurView.bounds)
path.usesEvenOddFillRule = true
path.append(UIBezierPath(rect: CGRect(x: 100, y: 100, width: 100, height: 100)))
path.append(UIBezierPath(rect: CGRect(x: 150, y: 150, width: 100, height: 100)))
//here you can add more paths, but the number is not known
let layer = CAShapeLayer()
layer.path = path.cgPath
layer.fillRule = .evenOdd
blurView.layer.mask = layer
and the effect is following:
Two rectangles overlapping one another. But all I need is to combine area from both rectanges, not to exclude everlapping area. Is it possible?
Using the "even-odd" fill rule is great for "cutting a hole" in a path. However, this code:
// create a big rect
let path = UIBezierPath(rect: blurView.bounds)
// cut a hole in it
path.append(UIBezierPath(rect: CGRect(x: 100, y: 100, width: 100, height: 100)))
// cut a hole overlapping a hole?
path.append(UIBezierPath(rect: CGRect(x: 150, y: 150, width: 100, height: 100)))
will be, as you've seen, problematic.
Depending on what all you are wanting to do, you could use a library such as ClippingBezier which allows you to manipulate paths with boolean actions.
Or, you can use a custom CALayer like this to "invert" multiple paths to use as a "cutout mask":
class BasicCutoutLayer: CALayer {
var rects: [CGRect] = []
func addRect(_ newRect: CGRect) {
rects.append(newRect)
setNeedsDisplay()
}
func reset() {
rects = []
setNeedsDisplay()
}
override func draw(in ctx: CGContext) {
// fill entire layer with solid color
ctx.setFillColor(UIColor.gray.cgColor)
ctx.fill(self.bounds);
rects.forEach { r in
ctx.addPath(UIBezierPath(rect: r).cgPath)
}
// draw clear "cutouts"
ctx.setFillColor(UIColor.clear.cgColor)
ctx.setBlendMode(.sourceIn)
ctx.drawPath(using: .fill)
}
}
To show it in use, we'll use this image:
In a standard UIImageView, overlaid with a blur UIVisualEffectView, and then use the BasicCutoutLayer class with two overlapping rects as the blur view's layer mask:
class BasicCutoutVC: UIViewController {
let myBlurView = UIVisualEffectView()
let myCutoutLayer = BasicCutoutLayer()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
let imgView = UIImageView()
if let img = UIImage(named: "sampleBG") {
imgView.image = img
}
[imgView, myBlurView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
imgView.topAnchor.constraint(equalTo: g.topAnchor),
imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
imgView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
imgView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
myBlurView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
myBlurView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
myBlurView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
myBlurView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
myBlurView.effect = UIBlurEffect(style: .extraLight)
// set mask for blur view
myBlurView.layer.mask = myCutoutLayer
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// set mask layer frame
myCutoutLayer.frame = myBlurView.bounds
// add two overlapping rects
let v: CGFloat = 160
let c: CGPoint = CGPoint(x: myBlurView.bounds.midX, y: myBlurView.bounds.midY)
var r: CGRect = CGRect(origin: c, size: CGSize(width: v, height: v))
r.origin.x -= v * 0.75
r.origin.y -= v * 0.75
myCutoutLayer.addRect(r)
r.origin.x += v * 0.5
r.origin.y += v * 0.5
myCutoutLayer.addRect(r)
}
}
Before applying the mask, it looks like this:
after applying the mask we get:
As we see, the "overlap" displays as we want.
That was a very simple, basic example. For a more advanced example, take a look at this:
struct MyPath {
var lineWidth: CGFloat = 0
var lineCap: CGLineCap = .butt
var lineJoin: CGLineJoin = .bevel
var isStroked: Bool = true
var isFilled: Bool = true
var pth: UIBezierPath = UIBezierPath()
}
class AdvancedCutoutLayer: CALayer {
var myPaths: [MyPath] = []
func addPath(_ newPath: MyPath) {
myPaths.append(newPath)
setNeedsDisplay()
}
func reset() {
myPaths = []
setNeedsDisplay()
}
override func draw(in ctx: CGContext) {
// fill entire layer with solid color
ctx.setFillColor(UIColor.gray.cgColor)
ctx.fill(self.bounds);
ctx.setBlendMode(.sourceIn)
myPaths.forEach { thisPath in
ctx.setStrokeColor(thisPath.isStroked ? UIColor.clear.cgColor : UIColor.black.cgColor)
ctx.setFillColor(thisPath.isFilled ? UIColor.clear.cgColor : UIColor.black.cgColor)
ctx.setLineWidth(thisPath.isStroked ? thisPath.lineWidth : 0.0)
ctx.setLineCap(thisPath.lineCap)
ctx.setLineJoin(thisPath.lineJoin)
ctx.addPath(thisPath.pth.cgPath)
ctx.drawPath(using: .fillStroke)
}
}
}
along with a subclassed UIVisualEffectView for convenience:
class CutoutBlurView: UIVisualEffectView {
let sl = AdvancedCutoutLayer()
override init(effect: UIVisualEffect?) {
super.init(effect: effect)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
sl.isOpaque = false
layer.mask = sl
}
override func layoutSubviews() {
super.layoutSubviews()
sl.frame = bounds
sl.setNeedsDisplay()
}
func addPath(_ newPath: MyPath) {
sl.addPath(newPath)
}
func reset() {
sl.reset()
}
}
and an example controller:
class AdvancedCutoutVC: UIViewController {
let myView = CutoutBlurView()
var idx: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
let imgView = UIImageView()
if let img = UIImage(named: "sampleBG") {
imgView.image = img
}
[imgView, myView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
imgView.topAnchor.constraint(equalTo: g.topAnchor),
imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
imgView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
imgView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
myView.topAnchor.constraint(equalTo: g.topAnchor),
myView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
myView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
myView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
myView.effect = UIBlurEffect(style: .extraLight)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true, block: { _ in
switch self.idx % 4 {
case 1:
self.addSomeOvals()
case 2:
self.addSomeLines()
case 3:
self.addSomeShapes()
default:
self.addSomeRects()
}
self.idx += 1
})
}
func addSomeRects() {
myView.reset()
let w: CGFloat = myView.frame.width / 4.0
let h: CGFloat = myView.frame.height / 4.0
var x: CGFloat = ((myView.frame.width - (w * 5.0 * 0.5)) * 0.5) - (w * 0.25)
var y: CGFloat = ((myView.frame.height - (h * 5.0 * 0.5)) * 0.5) - (h * 0.25)
for _ in 1...5 {
let bz = UIBezierPath(rect: CGRect(x: x, y: y, width: w, height: h))
myView.addPath(MyPath(lineWidth: 0, isStroked: false, isFilled: true, pth: bz))
x += w * 0.5
y += h * 0.5
}
}
func addSomeOvals() {
myView.reset()
let w: CGFloat = myView.frame.width / 4.0
let h: CGFloat = myView.frame.height / 4.0
var x: CGFloat = ((myView.frame.width - (w * 5.0 * 0.5)) * 0.5) - (w * 0.25)
var y: CGFloat = ((myView.frame.height - (h * 5.0 * 0.5)) * 0.5) - (h * 0.25)
for _ in 1...5 {
let bz = UIBezierPath(ovalIn: CGRect(x: x, y: y, width: w, height: h))
myView.addPath(MyPath(lineWidth: 0, isStroked: false, isFilled: true, pth: bz))
x += w * 0.5
y += h * 0.5
}
}
func addSomeLines() {
myView.reset()
let w: CGFloat = myView.frame.width / 2.0
let h: CGFloat = myView.frame.height / 4.0
let x: CGFloat = 80
var y: CGFloat = 80
var lw: CGFloat = 4
for _ in 1...5 {
let bz = UIBezierPath()
bz.move(to: CGPoint(x: x, y: y))
bz.addLine(to: CGPoint(x: x + w, y: y + 20))
myView.addPath(MyPath(lineWidth: lw, lineCap: .round, isStroked: true, isFilled: false, pth: bz))
y += h * 0.5
lw += 10
}
}
func addSomeShapes() {
myView.reset()
var bz: UIBezierPath!
bz = UIBezierPath(rect: CGRect(x: 80, y: 80, width: 80, height: 120))
myView.addPath(MyPath(isStroked: false, isFilled: true, pth: bz))
bz = UIBezierPath(rect: CGRect(x: 120, y: 120, width: 120, height: 60))
myView.addPath(MyPath(isStroked: false, isFilled: true, pth: bz))
bz = UIBezierPath(rect: CGRect(x: 80, y: 220, width: 220, height: 60))
myView.addPath(MyPath(lineWidth: 12, isStroked: true, isFilled: false, pth: bz))
bz = UIBezierPath(ovalIn: CGRect(x: 100, y: 240, width: 220, height: 60))
myView.addPath(MyPath(lineWidth: 12, isStroked: true, isFilled: false, pth: bz))
var r: CGRect = CGRect(x: 40, y: 320, width: myView.frame.width - 80, height: 200)
for _ in 1...4 {
bz = UIBezierPath(rect: r)
myView.addPath(MyPath(lineWidth: 8, isStroked: true, isFilled: false, pth: bz))
r = r.insetBy(dx: 20, dy: 20)
}
}
}
When run, this example will cycle through overlapping rect, overlapping ovals, some varying width lines, and some assorted shapes (just to give an idea):
I would go with ClippingBezier because it is fast, easy to use and neat. It'll be something like this:
let rect1 = CGRect(x: 100, y: 100, width: 200, height: 200)
let rect2 = CGRect(x: 150, y: 200, width: 200, height: 200)
let path0 = UIBezierPath(rect: blurView.bounds)
let path1 = UIBezierPath(rect: rect1)
let path2 = UIBezierPath(rect: rect2)
let unionPathArray = path1.union(with: path2)
let unionPath = UIBezierPath()
if let array = unionPathArray {
array.forEach(unionPath.append)
path0.append(unionPath.reversing())
let layerUnion = CAShapeLayer()
layerUnion.path = path0.cgPath
blurView.layer.mask = layerUnion
}
Output:
EDIT
It appears that this method doesn't work properly when using UIBezierPath(roundedRect:cornerRadius:). To overcome that, here is how we can construct our own func to do that:
extension UIBezierPath {
convenience init(rectangleIn rect: CGRect, cornerRadius: CGFloat) {
self.init()
move(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius))
addArc(withCenter: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + cornerRadius), radius: cornerRadius, startAngle: .pi, endAngle: 3.0 * .pi / 2.0, clockwise: true)
addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY))
addArc(withCenter: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY + cornerRadius), radius: cornerRadius, startAngle: 3.0 * .pi / 2.0, endAngle: 2 * .pi, clockwise: true)
addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius))
addArc(withCenter: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - cornerRadius), radius: cornerRadius, startAngle: 0.0, endAngle: .pi / 2.0, clockwise: true)
addLine(to: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY))
addArc(withCenter: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY - cornerRadius), radius: cornerRadius, startAngle: .pi / 2.0, endAngle: .pi, clockwise: true)
//addLine(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius))
close()
}
}
We can also extend the above-mentioned solution to multiple paths. Here is one way to create the union of multiple paths:
extension UIBezierPath {
class func getUnion(of paths: [UIBezierPath]) -> UIBezierPath {
var result = UIBezierPath()
paths.forEach { subPath in
guard let union = result.union(with: subPath) else { return }
let unionCombined = UIBezierPath()
union.forEach(unionCombined.append)
result = unionCombined
}
return result
}
}
Here is an example:
let rect1 = CGRect(x: 100, y: 100, width: 200, height: 180)
let rect2 = CGRect(x: 150, y: 200, width: 200, height: 200)
let rect3 = CGRect(x: 150, y: 500, width: 100, height: 100)
let rect4 = CGRect(x: 150, y: 800, width: 300, height: 100)
let pathBase = UIBezierPath(rect: blurView.bounds)
let path1 = UIBezierPath(rectangleIn: rect1, cornerRadius: 20.0)
let path2 = UIBezierPath(rect: rect2)
let path3 = UIBezierPath(ovalIn: rect3)
let path4 = UIBezierPath(ovalIn: rect4)
let union = UIBezierPath.getUnion(of: [path1, path2, path3, path4])
pathBase.append(union.reversing())
let layerUnion = CAShapeLayer()
layerUnion.path = pathBase.cgPath
blurView.layer.mask = layerUnion
And the output:

Swift Tabbar with a custom shape in the middle

i want to make this shape in swift .
As you can see, the tabbar has a raised center button. However, this is not the only thing as there should be a real hole in the tabbar so that it is transparent there.
How can I create such a hole inside a tabbar? And then put a raised, round button in that hole?
I would gladly appreciate any help regarding my question.
i am trying but cannot achieve the above result.
import Foundation
import UIKit
#IBDesignable
class AppTabBar: UITabBar {
private var shapeLayer: CALayer?
override func draw(_ rect: CGRect) {
self.addShape()
}
private func addShape() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = createPath()
shapeLayer.strokeColor = UIColor.lightGray.cgColor
shapeLayer.fillColor = #colorLiteral(red: 0.9782002568, green: 0.9782230258, blue: 0.9782107472, alpha: 1)
shapeLayer.lineWidth = 0.5
// The below 4 lines are for shadow above the bar. you can skip them if you do not want a shadow
shapeLayer.shadowOffset = CGSize(width:0, height:0)
shapeLayer.shadowRadius = 10
shapeLayer.shadowColor = UIColor.gray.cgColor
shapeLayer.shadowOpacity = 0.3
if let oldShapeLayer = self.shapeLayer {
self.layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
} else {
self.layer.insertSublayer(shapeLayer, at: 0)
}
self.shapeLayer = shapeLayer
}
func createPath() -> CGPath {
let height2: CGFloat = self.frame.height
let height: CGFloat = 86.0
let path = UIBezierPath()
let centerWidth = self.frame.width / 2
let startXpoint = centerWidth - height + 57
let endXpoint = (centerWidth + height - 45)
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: startXpoint , y: 0))
// path.addCurve(to: CGPoint(x: centerWidth, y: height - 40),
// controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 35, y: height - 40))
path.addCurve(to: CGPoint(x: centerWidth, y: height / 1.6),
controlPoint1: CGPoint(x: startXpoint - 5, y: height2 / 3), controlPoint2: CGPoint(x: (centerWidth - 10), y: height2 / 1.6))
path.addCurve(to: CGPoint(x: (centerWidth + height / 2.9 ), y: 0),
controlPoint1: CGPoint(x: centerWidth + 35, y: height - 40), controlPoint2: CGPoint(x: (centerWidth + 30), y: 0))
path.addLine(to: CGPoint(x: self.frame.width, y: 0))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height))
path.addLine(to: CGPoint(x: 0, y: self.frame.height))
path.close()
return path.cgPath
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard !clipsToBounds && !isHidden && alpha > 0 else { return nil }
for member in subviews.reversed() {
let subPoint = member.convert(point, from: self)
guard let result = member.hitTest(subPoint, with: event) else { continue }
return result
}
return nil
}
}
extension UITabBar {
override open func sizeThatFits(_ size: CGSize) -> CGSize {
var sizeThatFits = super.sizeThatFits(size)
sizeThatFits.height = 74
return sizeThatFits
}
}

Mask Image with custom UIBazierPath swift

I have created UIBezierPath with custom shape then I need to make it mask for image always I got empty image
here is my code
First I created the path, then create image and last create my mask but it is not working
here is image I need to mask it dropbox.com/s/tnxgx7g1uvb1zj7/TeethMask.png?dl=0 here is UIBazier path dropbox.com/s/nz93n1vgvj6c6y0/… I need to mask this image in this path
The output is something like this
https://www.dropbox.com/s/gueyhdmmdcfvyiq/image.png?dl=0
Here is ViewController class
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapGR = UITapGestureRecognizer(target: self, action: #selector(didTap))
self.view.addGestureRecognizer(tapGR)
}
#objc func didTap(tapGR: UITapGestureRecognizer) {
let tapPoint = tapGR.location(in: self.view)
if #available(iOS 11.0, *) {
let shapeView = ShapeView(origin: tapPoint)
self.view.addSubview(shapeView)
} else {
// Fallback on earlier versions
}
}
}
Here is ShapeView class
import UIKit
#available(iOS 11.0, *)
class ShapeView: UIView {
let size: CGFloat = 150
let lineWidth: CGFloat = 3
var fillColor: UIColor!
var path: UIBezierPath!
init(origin: CGPoint) {
super.init(frame: CGRect(x: 0.0, y: 0.0, width: size, height: size))
self.fillColor = randomColor()
self.path = mouthPath()
self.center = origin
self.backgroundColor = UIColor.clear
}
func randomColor() -> UIColor {
let hue:CGFloat = CGFloat(Float(arc4random()) / Float(UINT32_MAX))
return UIColor(hue: hue, saturation: 0.8, brightness: 1.0, alpha: 0.8)
}
func mouthPath() -> UIBezierPath{
let pointsArray = [CGPoint(x:36 , y:36 ),CGPoint(x:41 , y:36 ),CGPoint(x:45 , y:36 ),CGPoint(x:49 , y:36 ),CGPoint(x:53 , y:36 ),CGPoint(x:58 , y: 37),CGPoint(x:64 , y:37 ),CGPoint(x:69 , y:36 ),CGPoint(x:65 , y:29 ),CGPoint(x:58 , y:24 ),CGPoint(x:50 , y:22 ),CGPoint(x:42 , y:23 ),CGPoint(x:36 , y:28 ),CGPoint(x:32 , y:35 )]
let newPath = UIBezierPath()
let factor:CGFloat = 10
for i in 0...pointsArray.count - 1 { // last point is 0,0
let point = pointsArray[i]
let currentPoint1 = CGPoint(x: point.x * factor , y: point.y * factor)
if i == 0 {
newPath.move(to: currentPoint1)
} else {
newPath.addLine(to: currentPoint1)
}
}
newPath.addLine(to: CGPoint(x: pointsArray[0].x * factor, y: pointsArray[0].y * factor))
newPath.close()
let imageTemplate = UIImageView()
imageTemplate.image = UIImage(named: "TeethMask")
self.addSubview(imageTemplate)
self.bringSubviewToFront(imageTemplate)
imageTemplate.frame = self.frame
let mask = CAShapeLayer(layer: self.layer)
mask.frame = newPath.bounds
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.black.cgColor
mask.path = newPath.cgPath
mask.shouldRasterize = true
imageTemplate.layer.mask = mask
imageTemplate.layer.addSublayer(mask)
}
}
Well, you're doing a few things wrong...
The "teeth" image you linked:
has a native size of 461 x 259. So, I'm going to use a proportional "target" size of 200 x 112.
First, shape layers use 0,0 at upper-left. Your original points array:
let pointsArray = [
CGPoint(x: 36, y: 36),
CGPoint(x: 41, y: 36),
CGPoint(x: 45, y: 36),
CGPoint(x: 49, y: 36),
CGPoint(x: 53, y: 36),
CGPoint(x: 58, y: 37),
CGPoint(x: 64, y: 37),
CGPoint(x: 69, y: 36),
CGPoint(x: 65, y: 29),
CGPoint(x: 58, y: 24),
CGPoint(x: 50, y: 22),
CGPoint(x: 42, y: 23),
CGPoint(x: 36, y: 28),
CGPoint(x: 32, y: 35),
]
gives this shape:
If we invert the y-coordinates:
let pointsArray = [
CGPoint(x: 36.0, y: 23.0),
CGPoint(x: 41.0, y: 23.0),
CGPoint(x: 45.0, y: 23.0),
CGPoint(x: 49.0, y: 23.0),
CGPoint(x: 53.0, y: 23.0),
CGPoint(x: 58.0, y: 22.0),
CGPoint(x: 64.0, y: 22.0),
CGPoint(x: 69.0, y: 23.0),
CGPoint(x: 65.0, y: 30.0),
CGPoint(x: 58.0, y: 35.0),
CGPoint(x: 50.0, y: 37.0),
CGPoint(x: 42.0, y: 36.0),
CGPoint(x: 36.0, y: 31.0),
CGPoint(x: 32.0, y: 24.0),
]
we get this shape:
It will be difficult to get things to "line up" correctly if your shape is offset like that, so we can "normalize" the points to start at top-left:
let pointsArray: [CGPoint] = [
CGPoint(x: 4.0, y: 1.0),
CGPoint(x: 9.0, y: 1.0),
CGPoint(x: 13.0, y: 1.0),
CGPoint(x: 17.0, y: 1.0),
CGPoint(x: 21.0, y: 1.0),
CGPoint(x: 26.0, y: 0.0),
CGPoint(x: 32.0, y: 0.0),
CGPoint(x: 37.0, y: 1.0),
CGPoint(x: 33.0, y: 8.0),
CGPoint(x: 26.0, y: 13.0),
CGPoint(x: 18.0, y: 15.0),
CGPoint(x: 10.0, y: 14.0),
CGPoint(x: 4.0, y: 9.0),
CGPoint(x: 0.0, y: 2.0),
]
resulting in:
However, we want the shape to fit the image, so we can scale the UIBezierPath to the bounds of the imageView:
// need to scale the path to self.bounds
let scaleW = bounds.width / pth.bounds.width
let scaleH = bounds.height / pth.bounds.height
let trans = CGAffineTransform(scaleX: scaleW, y: scaleH)
pth.apply(trans)
and we're here:
The only thing left is to use that as a mask for the image.
I'm going to suggest subclassing UIImageView instead of UIView ... that way you can set the .image property without needing to add another view as a subview. Also, I think you'll find it much easier to manage the size of the custom, masked image in your controller code, rather than inside the custom class.
Here is a demo view controller and a custom MouthShapeView:
class TeethViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapGR = UITapGestureRecognizer(target: self, action: #selector(didTap))
self.view.addGestureRecognizer(tapGR)
}
#objc func didTap(tapGR: UITapGestureRecognizer) {
let tapPoint = tapGR.location(in: self.view)
if #available(iOS 11.0, *) {
// make sure you can load the image
if let img = UIImage(named: "TeethMask") {
// create custom ShapeView with image
let shapeView = MouthShapeView(image: img)
// if you want to use original image proportions
// set the width you want and calculate a proportional height
// based on the original image size
let targetWidth: CGFloat = 200.0
let targetHeight: CGFloat = img.size.height / img.size.width * targetWidth
// set the frame size
shapeView.frame.size = CGSize(width: targetWidth, height: targetHeight)
// set the frame center
shapeView.center = tapPoint
// add it
self.view.addSubview(shapeView)
}
} else {
// Fallback on earlier versions
}
}
}
#available(iOS 11.0, *) class MouthShapeView: UIImageView {
let pointsArray: [CGPoint] = [
CGPoint(x: 4.0, y: 1.0),
CGPoint(x: 9.0, y: 1.0),
CGPoint(x: 13.0, y: 1.0),
CGPoint(x: 17.0, y: 1.0),
CGPoint(x: 21.0, y: 1.0),
CGPoint(x: 26.0, y: 0.0),
CGPoint(x: 32.0, y: 0.0),
CGPoint(x: 37.0, y: 1.0),
CGPoint(x: 33.0, y: 8.0),
CGPoint(x: 26.0, y: 13.0),
CGPoint(x: 18.0, y: 15.0),
CGPoint(x: 10.0, y: 14.0),
CGPoint(x: 4.0, y: 9.0),
CGPoint(x: 0.0, y: 2.0),
]
let maskLayer = CAShapeLayer()
override init(image: UIImage?) {
super.init(image: image)
maskLayer.fillColor = UIColor.black.cgColor
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
let newPath = UIBezierPath()
pointsArray.forEach { p in
if p == pointsArray.first {
newPath.move(to: p)
} else {
newPath.addLine(to: p)
}
}
newPath.close()
// need to scale the path to self.bounds
let scaleW = bounds.width / newPath.bounds.width
let scaleH = bounds.height / newPath.bounds.height
let trans = CGAffineTransform(scaleX: scaleW, y: scaleH)
newPath.apply(trans)
maskLayer.path = newPath.cgPath
layer.mask = maskLayer
}
}
When you run that code, and tap on the view, you'll get this:

Create CAGradient Layer with transparent hole in it?

Currently I do it like this:
final class BorderedButton: BottomNavigationButton {
private let gradient = CAGradientLayer()
private let shapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let radius = bounds.size.height / 2
let outside = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height), cornerRadius: radius)
let inside = UIBezierPath(roundedRect: CGRect(x: 3.0, y: 3.0, width: bounds.width - 6, height: bounds.height - 6), cornerRadius: radius - 3)
outside.append(inside)
outside.usesEvenOddFillRule = true
shapeLayer.path = outside.cgPath
}
init(color: UIColor?) {
super.init(frame: .zero)
let isGradient = color == nil
shapeLayer.fillRule = kCAFillRuleEvenOdd
shapeLayer.fillColor = UIColor.red.cgColor
layer.insertSublayer(shapeLayer, at: 0)
//gradient part
gradient.colors = isGradient ? [Constants.gradientStart, Constants.gradientEnd] : [color!.cgColor, color!.cgColor]
gradient.startPoint = CGPoint(x: 0.2, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 1)
}
}
How can I apply that gradient to my code?
Don't add the CAShapeLayer to the view's layer, rather set it as the mask of the CAGradientLayer. Also don't forget to set the bounds of the gradient layer.
I had to make some modifications to get it to run in a playground, but this works for me:
let gradientStart = UIColor.orange
let gradientEnd = UIColor.blue
final class BorderedButton: UIButton {
private let gradient = CAGradientLayer()
private let shapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let radius = bounds.size.height / 2
let outside = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height), cornerRadius: radius)
let inside = UIBezierPath(roundedRect: CGRect(x: 3.0, y: 3.0, width: bounds.width - 6, height: bounds.height - 6), cornerRadius: radius - 3)
outside.append(inside)
outside.usesEvenOddFillRule = true
shapeLayer.path = outside.cgPath
gradient.frame = CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height)
}
init(color: UIColor?, frame: CGRect = .zero) {
super.init(frame: frame)
let isGradient = color == nil
shapeLayer.fillRule = kCAFillRuleEvenOdd
//gradient part
gradient.colors = isGradient ? [gradientStart.cgColor, gradientEnd.cgColor] : [color!.cgColor, color!.cgColor]
gradient.startPoint = CGPoint(x: 0.2, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 1)
gradient.mask = shapeLayer
layer.addSublayer(gradient)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Adding inner shadow to top of UIView

I looked up but I couldn't find how I can add an inner shadow to UIView, only top (from top to bottom) for Swift. What is the best way add inner circle in Swift?
Edit: I've found some questions & answers on SO however they are either in obj-c or looks so complicated. I was just looking for a more Swifty way, if there is any
What I want to achieve:
Here's a pure Swift version that I whipped up:
public class EdgeShadowLayer: CAGradientLayer {
public enum Edge {
case Top
case Left
case Bottom
case Right
}
public init(forView view: UIView,
edge: Edge = Edge.Top,
shadowRadius radius: CGFloat = 20.0,
toColor: UIColor = UIColor.white,
fromColor: UIColor = UIColor.black) {
super.init()
self.colors = [fromColor.cgColor, toColor.cgColor]
self.shadowRadius = radius
let viewFrame = view.frame
switch edge {
case .Top:
startPoint = CGPoint(x: 0.5, y: 0.0)
endPoint = CGPoint(x: 0.5, y: 1.0)
self.frame = CGRect(x: 0.0, y: 0.0, width: viewFrame.width, height: shadowRadius)
case .Bottom:
startPoint = CGPoint(x: 0.5, y: 1.0)
endPoint = CGPoint(x: 0.5, y: 0.0)
self.frame = CGRect(x: 0.0, y: viewFrame.height - shadowRadius, width: viewFrame.width, height: shadowRadius)
case .Left:
startPoint = CGPoint(x: 0.0, y: 0.5)
endPoint = CGPoint(x: 1.0, y: 0.5)
self.frame = CGRect(x: 0.0, y: 0.0, width: shadowRadius, height: viewFrame.height)
case .Right:
startPoint = CGPoint(x: 1.0, y: 0.5)
endPoint = CGPoint(x: 0.0, y: 0.5)
self.frame = CGRect(x: viewFrame.width - shadowRadius, y: 0.0, width: shadowRadius, height: viewFrame.height)
}
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
To use it,
let topShadow = EdgeShadowLayer(forView: targetView, edge: .Top)
targetView.layer.addSublayer(topShadow)
Note that it defaults to a black-to-white gradient that's 20 points deep.
The full code, with a sample UIViewController that lets you toggle shadows on all four corners of a view, can be found at https://github.com/jrtibbetts/Tenebrae. I've also documented the EdgeShadowLayer pretty thoroughly.
I used implement inner shadow to UIView using Objective-C. I try to translate code into swift. Please forgive me for my poor swift syntax
you can call function below in UIView.didMoveToSuperview
func drawShadow() {
if nil == self.shadowLayer {
let size = self.frame.size
self.clipsToBounds = true
let layer: CALayer = CALayer()
layer.backgroundColor = UIColor.lightGrayColor().CGColor
layer.position = CGPointMake(size.width / 2, -size.height / 2 + 0.5)
layer.bounds = CGRectMake(0, 0, size.width, size.height)
layer.shadowColor = UIColor.darkGrayColor().CGColor
layer.shadowOffset = CGSizeMake(0.5, 0.5)
layer.shadowOpacity = 0.8
layer.shadowRadius = 5.0
self.shadowLayer = layer
self.layer.addSublayer(layer)
}
}
I tweaked the modification made by #anoop4real using clear as the toColor and made the interface more in-line with the shadow settings in CALayer, including defaults, with the exception of opacity, which is set to 0.0 by default. I went with a default of 0.6 since it looked the most natural.
extension UIView {
func addShadow(to edges: [UIRectEdge], radius: CGFloat = 3.0, opacity: Float = 0.6, color: CGColor = UIColor.black.cgColor) {
let fromColor = color
let toColor = UIColor.clear.cgColor
let viewFrame = self.frame
for edge in edges {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [fromColor, toColor]
gradientLayer.opacity = opacity
switch edge {
case .top:
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
gradientLayer.frame = CGRect(x: 0.0, y: 0.0, width: viewFrame.width, height: radius)
case .bottom:
gradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.frame = CGRect(x: 0.0, y: viewFrame.height - radius, width: viewFrame.width, height: radius)
case .left:
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.frame = CGRect(x: 0.0, y: 0.0, width: radius, height: viewFrame.height)
case .right:
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.frame = CGRect(x: viewFrame.width - radius, y: 0.0, width: radius, height: viewFrame.height)
default:
break
}
self.layer.addSublayer(gradientLayer)
}
}
func removeAllShadows() {
if let sublayers = self.layer.sublayers, !sublayers.isEmpty {
for sublayer in sublayers {
sublayer.removeFromSuperlayer()
}
}
}
}
The top view is the default settings, and the bottom uses a radius of 5.0 to show more clearly.
view1.addShadow([.top, .bottom, .left, .right])
view2.addShadow([.top, .bottom, .left, .right], radius: 5.0)
view2.backgroundColor = .orange
I updated #NRitH's answer and made an extension out of it also modified so that you can manipulate multiple edges in one go
usage
myview.addShadow(to: [.top,.bottom], radius: 15.0)
extension UIView{
func addShadow(to edges:[UIRectEdge], radius:CGFloat){
let toColor = UIColor(colorLiteralRed: 235.0/255.0, green: 235.0/255.0, blue: 235.0/255.0, alpha: 1.0)
let fromColor = UIColor(colorLiteralRed: 188.0/255.0, green: 188.0/255.0, blue: 188.0/255.0, alpha: 1.0)
// Set up its frame.
let viewFrame = self.frame
for edge in edges{
let gradientlayer = CAGradientLayer()
gradientlayer.colors = [fromColor.cgColor,toColor.cgColor]
gradientlayer.shadowRadius = radius
switch edge {
case UIRectEdge.top:
gradientlayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradientlayer.endPoint = CGPoint(x: 0.5, y: 1.0)
gradientlayer.frame = CGRect(x: 0.0, y: 0.0, width: viewFrame.width, height: gradientlayer.shadowRadius)
case UIRectEdge.bottom:
gradientlayer.startPoint = CGPoint(x: 0.5, y: 1.0)
gradientlayer.endPoint = CGPoint(x: 0.5, y: 0.0)
gradientlayer.frame = CGRect(x: 0.0, y: viewFrame.height - gradientlayer.shadowRadius, width: viewFrame.width, height: gradientlayer.shadowRadius)
case UIRectEdge.left:
gradientlayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientlayer.endPoint = CGPoint(x: 1.0, y: 0.5)
gradientlayer.frame = CGRect(x: 0.0, y: 0.0, width: gradientlayer.shadowRadius, height: viewFrame.height)
case UIRectEdge.right:
gradientlayer.startPoint = CGPoint(x: 1.0, y: 0.5)
gradientlayer.endPoint = CGPoint(x: 0.0, y: 0.5)
gradientlayer.frame = CGRect(x: viewFrame.width - gradientlayer.shadowRadius, y: 0.0, width: gradientlayer.shadowRadius, height: viewFrame.height)
default:
break
}
self.layer.addSublayer(gradientlayer)
}
}
func removeAllSublayers(){
if let sublayers = self.layer.sublayers, !sublayers.isEmpty{
for sublayer in sublayers{
sublayer.removeFromSuperlayer()
}
}
}
}
Swift 5 extension
extension UIView {
func addInnerShadow() {
let innerShadow = CALayer()
innerShadow.frame = bounds
// Shadow path (1pt ring around bounds)
let radius = self.layer.cornerRadius
let path = UIBezierPath(roundedRect: innerShadow.bounds.insetBy(dx: 2, dy:2), cornerRadius:radius)
let cutout = UIBezierPath(roundedRect: innerShadow.bounds, cornerRadius:radius).reversing()
path.append(cutout)
innerShadow.shadowPath = path.cgPath
innerShadow.masksToBounds = true
// Shadow properties
innerShadow.shadowColor = UIColor.black.cgColor
innerShadow.shadowOffset = CGSize(width: 0, height: 0)
innerShadow.shadowOpacity = 0.5
innerShadow.shadowRadius = 2
innerShadow.cornerRadius = self.layer.cornerRadius
layer.addSublayer(innerShadow)
}
}
I rewrote #NRitH solution on Swift 3, also slightly refactor it:
final class SideShadowLayer: CAGradientLayer {
enum Side {
case top,
bottom,
left,
right
}
init(frame: CGRect, side: Side, shadowWidth: CGFloat,
fromColor: UIColor = .black,
toColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0),
opacity: Float = 0.5) {
super.init()
colors = [fromColor.cgColor, toColor.cgColor]
self.opacity = opacity
switch side {
case .bottom:
startPoint = CGPoint(x: 0.5, y: 1.0)
endPoint = CGPoint(x: 0.5, y: 0.0)
self.frame = CGRect(x: 0, y: frame.height - shadowWidth, width: frame.width, height: shadowWidth)
case .top:
startPoint = CGPoint(x: 0.5, y: 0.0)
endPoint = CGPoint(x: 0.5, y: 1.0)
self.frame = CGRect(x: 0, y: 0, width: frame.width, height: shadowWidth)
case .left:
startPoint = CGPoint(x: 0.0, y: 0.5)
endPoint = CGPoint(x: 1.0, y: 0.5)
self.frame = CGRect(x: 0, y: 0, width: shadowWidth, height: frame.height)
case .right:
startPoint = CGPoint(x: 1.0, y: 0.5)
endPoint = CGPoint(x: 0.0, y: 0.5)
self.frame = CGRect(x: frame.width - shadowWidth, y: 0, width: shadowWidth, height: frame.height)
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
If you don't mind using clipsToBounds = true, you can create a new CALayer offset just off the edge of your view and add the shadow to THAT view. This is what J.Hunter's answer does.
J.Hunter's code adds a top shadow, here I updated it to Swift 5 and added it to the bottom.
Swift 5:
override func draw(_ rect: CGRect) {
// Create Inner Shadow. Not sure about efficiency of this.
// You may want to create a shadowLayer property
// and only run this code if it hasn't been created yet.
let size = rect.size
clipsToBounds = true // Don't want to see your fake view layer
let innerShadowLayer: CALayer = CALayer()
// Need to set a backgroundColor or it doesn't work
innerShadowLayer.backgroundColor = UIColor.black.cgColor
// Position your shadow layer (anchor point is in the center)
// on the edge of where your shadow needs to be.
// In my case this moves the shadow layer to the
// bottom edge of my view
innerShadowLayer.position = CGPoint(x: size.width / 2, y: size.height + (size.height / 2))
// This could be smaller I think, just copying J.Hunter's code...
innerShadowLayer.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height)
// Normal shadow layer properties you'd use for an outer shadow
innerShadowLayer.shadowColor = UIColor.black.cgColor
innerShadowLayer.shadowOffset = CGSize(width: 0, height: 0)
innerShadowLayer.shadowOpacity = 0.3
innerShadowLayer.shadowRadius = 3
layer.addSublayer(innerShadowLayer)
}

Resources