Adding inner shadow to top of UIView - ios

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)
}

Related

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:

Custom change color of cell bottom border

I need to change color of bottom cell in UICollectionView, in this question just I do this
I need set color to bottom cell like this
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor(red: 184/255, green: 215/255, blue: 215/255, alpha: 1).cgColor
border.frame = CGRect(x: 0, y: cell.frame.size.height - width, width: cell.frame.size.width, height: cell.frame.size.height)
border.borderWidth = width
cell.layer.addSublayer(border)
cell.layer.masksToBounds = true
Instead of trying to add it as a border, I would add two layers instead as it is much easier. Something like :
override func layoutSubviews() {
super.layoutSubviews()
backgroundShape = CAShapeLayer.init()
backPath = UIBezierPath.init(rect: self.bounds)// Use your path
backgroundShape.path = backPath.cgPath
backgroundShape.fillColor = UIColor.blue.cgColor//border color in your case
self.layer.addSublayer(backgroundShape)
foregroundShape = CAShapeLayer()
forgroundPath = UIBezierPath.init(rect: CGRect.init(x: 0, y: -20, width: self.bounds.width, height: self.bounds.height))// Use your path with a little negative y
foregroundShape.path = forgroundPath.cgPath
foregroundShape.fillColor = UIColor.yellow.cgColor//white in your case
self.layer.addSublayer(foregroundShape)
}
A detailed answer:
class BorderedCell: UICollectionViewCell{
var backgroundShape: CAShapeLayer!
var backPath: UIBezierPath!
var foregroundShape: CAShapeLayer!
var forgroundPath: UIBezierPath!
override func layoutSubviews() {
super.layoutSubviews()
backgroundShape = CAShapeLayer.init()
backPath = drawCurvedShape(with: 0)
backgroundShape.path = backPath.cgPath
backgroundShape.fillColor = UIColor(red:0.76, green:0.86, blue:0.86, alpha:1.0).cgColor
self.layer.addSublayer(backgroundShape)
foregroundShape = CAShapeLayer()
forgroundPath = drawCurvedShape(with: -8)
foregroundShape.path = forgroundPath.cgPath
foregroundShape.fillColor = UIColor.white.cgColor
self.layer.addSublayer(foregroundShape)
}
func drawCurvedShape(with startingY: CGFloat) -> UIBezierPath{
let path = UIBezierPath.init()
path.move(to: CGPoint.init(x: 0, y: startingY))
path.addLine(to: CGPoint.init(x: self.bounds.width, y: startingY))
path.addLine(to: CGPoint.init(x: self.bounds.width, y: self.bounds.height + startingY - 30))
path.addQuadCurve(to: CGPoint.init(x: self.bounds.width - 30, y: self.bounds.height + startingY), controlPoint: CGPoint.init(x: self.bounds.width, y: self.bounds.height + startingY))
path.addLine(to: CGPoint.init(x: 0, y: self.bounds.height + startingY))
path.addLine(to: CGPoint.init(x: 0, y: startingY))
path.close()
return path
}
}
Output:
I did not make the drawShape exactly to the shape you require, just to something that would serve the purpose.

How to create Gradient border with rounded corners at UIView in Swift 4

I want to set border color with the gradient left to right [Red, Green] at UIView.
Like example:
I tried below code: -
class View: UIView {
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: [.topLeft, .bottomLeft, .topRight, .bottomRight], cornerRadii: CGSize(width: frame.size.height / 2, height: frame.size.height / 2))
let gradient = CAGradientLayer()
gradient.frame = CGRect(origin: CGPoint.zero, size: frame.size)
gradient.colors = [UIColor.green.cgColor, UIColor.red.cgColor]
let shape = CAShapeLayer()
shape.lineWidth = 10
shape.path = path.cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradient.mask = shape
layer.insertSublayer(gradient, at: 0)
}
}
There is three problem which I am not able to resolve: -
1- I have set lineWidth 10 but its showing width 10 at corners and at horizontal/vertical only 5.
2- I want to show the gradient from left to right not top to bottom.
I tried below code to set gradient from left to right but not working: -
// gradient.frame = CGRect(origin: CGPoint.zero, size: frame.size)
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
Please help. Thanks in advance.
Edit
I want to set border from left to right
You need to change gradient.startPoint and gradient.endPoint
enum Direction {
case horizontal
case vertical
}
class View: UIView {
init(frame: CGRect, cornerRadius: CGFloat, colors: [UIColor], lineWidth: CGFloat = 5, direction: Direction = .horizontal) {
super.init(frame: frame)
self.layer.cornerRadius = cornerRadius
self.layer.masksToBounds = true
let gradient = CAGradientLayer()
gradient.frame = CGRect(origin: CGPoint.zero, size: self.frame.size)
gradient.colors = colors.map({ (color) -> CGColor in
color.cgColor
})
switch direction {
case .horizontal:
gradient.startPoint = CGPoint(x: 0, y: 1)
gradient.endPoint = CGPoint(x: 1, y: 1)
case .vertical:
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 0, y: 1)
}
let shape = CAShapeLayer()
shape.lineWidth = lineWidth
shape.path = UIBezierPath(roundedRect: self.bounds.insetBy(dx: lineWidth,
dy: lineWidth), cornerRadius: cornerRadius).cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradient.mask = shape
self.layer.addSublayer(gradient)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
As you can see, I added some extra parameters. I solved this issue by adding inset to the roundedRect:
shape.path = UIBezierPath(roundedRect: self.bounds.insetBy(dx: lineWidth,
dy: lineWidth), cornerRadius: cornerRadius).cgPath
How to use it:
let myView = View(frame: CGRect(x: 0, y: 0, width: 200, height: 50), cornerRadius: 25, colors: [UIColor.red, .orange, .yellow], lineWidth: 2, direction: .horizontal)
myView.center = view.center
view.addSubview(myView)
Screenshot:
roundedViewWithGradient
The problem is that the the bezier path is not drawn inside its bounds. It's drawn around its bounds. So half the stroke width is inside and half is outside. You need to adjust the bounds to deal with this.
Change the frame you pass into the bezier path from self.bounds to self.bounds.insetBy(dx: 5, dy: 5) where 5 is half your line width.
And the lines:
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
do result in the gradient going from left to right.
Here is fully working code from yours:
class View: UIView {
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath(roundedRect: self.bounds.insetBy(dx: 5, dy: 5), byRoundingCorners: [.topLeft, .bottomLeft, .topRight, .bottomRight], cornerRadii: CGSize(width: frame.size.height / 2, height: frame.size.height / 2))
let gradient = CAGradientLayer()
gradient.frame = CGRect(origin: CGPoint.zero, size: frame.size)
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.colors = [UIColor.green.cgColor, UIColor.red.cgColor]
let shape = CAShapeLayer()
shape.lineWidth = 10
shape.path = path.cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradient.mask = shape
layer.insertSublayer(gradient, at: 0)
}
}

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")
}
}

Extension of UIView

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()

Resources