Swift - Creating shadow with 2 different colours for imageView - swift

I'm wondering how to create a shadow with two different colours for an imageView. For example the top and left side has a different colour than the right and bottom side of the imageView.

To get different color shadows - one going up-left and one going down-right - on a UIImageView, one approach would be:
Subclass UIView
Give it 3 CALayer sublayers
Shadow 1 layer
Shadow 2 layer
Image layer
This also makes it easy to add rounded corners.
Here is a sample class. It has #IBInspectable properties to set the image, corner radius, shadow colors and shadow offsets. It is also marked #IBDesignable so you can see how it looks while designing in Storyboard / Interface Builder:
#IBDesignable
class DoubleShadowRoundedImageView: UIView {
#IBInspectable var image: UIImage? = nil {
didSet {
imageLayer.contents = image?.cgImage
}
}
#IBInspectable var cornerRadius: CGFloat = 0.0
#IBInspectable var shad1X: CGFloat = 0.0
#IBInspectable var shad1Y: CGFloat = 0.0
#IBInspectable var shad2X: CGFloat = 0.0
#IBInspectable var shad2Y: CGFloat = 0.0
#IBInspectable var shad1Color: UIColor = UIColor.blue
#IBInspectable var shad2Color: UIColor = UIColor.red
var imageLayer: CALayer = CALayer()
var shadowLayer1: CALayer = CALayer()
var shadowLayer2: CALayer = CALayer()
var shape: UIBezierPath {
return UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
}
var shapeAsPath: CGPath {
return shape.cgPath
}
var shapeAsMask: CAShapeLayer {
let s = CAShapeLayer()
s.path = shapeAsPath
return s
}
override func layoutSubviews() {
super.layoutSubviews()
clipsToBounds = false
backgroundColor = .clear
self.layer.addSublayer(shadowLayer1)
self.layer.addSublayer(shadowLayer2)
self.layer.addSublayer(imageLayer)
imageLayer.frame = bounds
imageLayer.mask = shapeAsMask
shadowLayer1.frame = bounds
shadowLayer2.frame = bounds
shadowLayer1.shadowPath = (image == nil) ? nil : shapeAsPath
shadowLayer1.shadowOpacity = 0.80
shadowLayer2.shadowPath = (image == nil) ? nil : shapeAsPath
shadowLayer2.shadowOpacity = 0.80
shadowLayer1.shadowColor = shad1Color.cgColor
shadowLayer2.shadowColor = shad2Color.cgColor
shadowLayer1.shadowOffset = CGSize(width: shad1X, height: shad1Y)
shadowLayer2.shadowOffset = CGSize(width: shad2X, height: shad2Y)
}
}
You would probably want to change some of the default values, and you might want to add some additional properties (such as shadow opacity).
Example results:

Related

How to create effect of stack of view for collection view?

I am try create effect like few view stack over other for collection view, I am not build effect like this in the past and can't understand how I can create this effect programmatically without images?
Can somebody explain me how I can repeat this effect or post link to code of tutorial?
Example of this effect below:
To get that effect you'd need to add layers under the collection view cells' layers that are smaller and shifted down a little from the cell's layers. You'd use the same background color as the cell on each layer that had a lower alpha than the cell's layer.
I created a sample project that demonstrates how to get the effect:
https://github.com/DuncanMC/CustomCollectionViewCell.git
The cells look like this:
The heavy lifting is in a custom subclass of UICollectionViewCell that I called MyCollectionViewCell
Here is that class:
class MyCollectionViewCell: UICollectionViewCell {
public var contentCornerRadius: CGFloat = 0 {
didSet {
contentView.layer.cornerRadius = contentCornerRadius
sizeLayerFrames()
}
}
public var fraction: CGFloat = 0.075 { // What percent to shrink & shift the faded layers down (0.075 = 7.5%)
didSet {
sizeLayerFrames()
}
}
private var layerMask = CAShapeLayer()
private var layer1 = CALayer()
private var layer2 = CALayer()
// Use this function to set the cell's background color.
// (You can't set the view's background color, since we Don't clip the view to it's bounds.)
// Be sure to set the background color explicitly, since by default it sets a random color that will persist
// as the cells are recylced, causing your cell colors to move around as the user scrolls
public func setBackgroundColor(_ color: UIColor) {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
color.getRed(&red, green: &green, blue: &blue, alpha: nil)
contentView.layer.backgroundColor = color.cgColor
//Make the first extra layer have the same color as the cell's layer, but with alpha 0.25
layer1.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 0.25).cgColor
//Make the second extra layer have the same color as the cell's layer, but with alpha 0.125
layer2.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 0.125).cgColor
}
#IBOutlet weak var customLabel: UILabel!
//Do The initial setup once the cell is loaded.
//Note that t
override func awakeFromNib() {
contentView.layer.masksToBounds = false
// Color each cell's layer some random hue (change to set whatever color you desire.)
//For testing, use a color based on a random hue and fairly high random brightness.
let hue = CGFloat.random(in: 0...360)
let brightness = CGFloat.random(in: 0.8...1.0)
layer.masksToBounds = false
setBackgroundColor(UIColor(hue: hue, saturation: 1, brightness: brightness, alpha: 1))
// Make the inside of the shape layer white (opaque), The color doesn't matter - just that the alpha value is 1
// and the outside clear (transparent)
layerMask.fillColor = UIColor.white.cgColor
layerMask.backgroundColor = UIColor.clear.cgColor
//With the even/odd rule, the inner shape will not be filled (we'll only fill the part NOT in the inner shape)
layerMask.fillRule = .evenOdd
contentCornerRadius = 30
sizeLayerFrames()
contentView.layer.addSublayer(layer1)
layer1.addSublayer(layer2)
layer1.mask = layerMask
}
private func sizeLayerFrames() {
layer1.cornerRadius = contentCornerRadius
layer2.cornerRadius = contentCornerRadius
let viewBounds = bounds //Use the layer's bounds as the starting point for the extra layers.
var frame1 = viewBounds
frame1.origin.y += viewBounds.size.height * fraction
frame1.origin.x += viewBounds.size.width * fraction
frame1.size.width *= CGFloat(1 - 2 * fraction)
layer1.frame = frame1
var frame2 = viewBounds
frame2.origin.y += viewBounds.size.height * 0.75 * fraction
frame2.origin.x += viewBounds.size.width * fraction
frame2.size.width *= CGFloat(1 - 4 * fraction)
layer2.frame = frame2
//Create a mask layer to clip the extra layers.
var maskFrame = viewBounds
//We are going to install the mask on layer1, so offeset the frame to cover the whole view contents
maskFrame.origin.y -= viewBounds.size.height * fraction
maskFrame.origin.x -= viewBounds.size.width * fraction
maskFrame.size.height += viewBounds.size.height * fraction * 1.75
layerMask.frame = maskFrame
maskFrame = viewBounds
let innerPath = UIBezierPath(roundedRect: maskFrame, cornerRadius: 30)
maskFrame.size.height += viewBounds.size.height * fraction * 1.75
let combinedPath = UIBezierPath(rect: maskFrame)
combinedPath.append(innerPath)
layerMask.path = combinedPath.cgPath
}
override var bounds: CGRect {
didSet {
sizeLayerFrames()
}
}
}

Strange animation occurs when extending UIView

I have a XUIView class as below. When I run animation, it's no effect for folding.
Who can explain me ?
class ViewController: UIViewController {
// Width constraint of XUIView instance
#IBOutlet weak var vwWrapperWidth: NSLayoutConstraint! {
didSet{
self.vwWrapperWidth.constant = UIScreen.main.bounds.width
}
}
#IBAction func btnToggleTouchUp(_ sender: UIButton) {
if(self.vwWrapperWidth.constant == 55) {
// animation effect is OK when expanding
self.vwWrapperWidth.constant = UIScreen.main.bounds.width
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
}
else {
// animation effect is not OK when folding
self.vwWrapperWidth.constant = 55
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
}
}
//.....
}
#IBDesignable
class XUIView: UIView {
#IBInspectable
var roundTopLeftCorner: Bool = false
#IBInspectable
var roundBottomLeftCorner: Bool = false
#IBInspectable
var roundTopRightCorner: Bool = false
#IBInspectable
var roundBottomRightCorner: Bool = false
#IBInspectable
var cornerRadius: CGFloat = 0.0
#IBInspectable
var borderWidth: CGFloat = 0.0
#IBInspectable
var borderColor: UIColor?
fileprivate var borderLayer: CAShapeLayer? {
didSet{
self.layer.addSublayer(self.borderLayer!)
}
}
func roundCorners(_ corners: UIRectCorner) {
if(self.borderLayer == nil) { self.borderLayer = CAShapeLayer() }
let bounds = self.bounds
let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: self.cornerRadius, height: self.cornerRadius))
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = maskPath.cgPath
self.layer.mask = maskLayer
self.borderLayer?.frame = bounds
self.borderLayer?.path = maskPath.cgPath
self.borderLayer?.strokeColor = self.borderColor?.cgColor
self.borderLayer?.lineWidth = self.borderWidth
self.borderLayer?.fillColor = nil
}
override func layoutSubviews() {
super.layoutSubviews()
var roundedCorners: UIRectCorner = []
if(roundTopLeftCorner) { roundedCorners.insert(.topLeft) }
if(roundTopRightCorner) { roundedCorners.insert(.topRight) }
if(roundBottomLeftCorner) { roundedCorners.insert(.bottomLeft) }
if(roundBottomRightCorner) { roundedCorners.insert(.bottomRight) }
roundCorners(roundedCorners)
}
}
source code : http://www.mediafire.com/file/n6svp1mk44fc0uf/TestXUIView.zip/file
You are experiencing no animation on the "folding" animation because CALayer's path property is not implicitly animatable. It is mentioned here.
Unlike most animatable properties, path (as with all CGPath animatable properties) does not support implicit animation.
To make matters worst, CALayer's frame property is not implicitly animatable either. It is mentioned here
Note: The frame property is not directly animatable. Instead you should animate the appropriate combination of the bounds, anchorPoint and position properties to achieve the desired result.
This simply means the following lines won't work, just because it is being called inside a UIView.animate block.
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds // This line won't animate!
maskLayer.path = maskPath.cgPath // This line won't animate!
self.layer.mask = maskLayer
Since it is not being animated, the mask is applied as soon as the layoutSubviews method in XUIView is called. View is actually animating, but you just cannot see it because the mask is being set before the animation completes.
This is also what happens in the expand animation. First the mask expands, then the animation occurs.
To prevent this, you might need to find a way to animate the width constraint and the layer frames & mask paths together.
The problem is that you are creating a CAShapeLayer in layoutSubviews which means that every time an animation occurs it gets call.
Try commenting line 91 and you are going to get what you want.

UIBezierPath: How to add a border around a view with rounded corners?

I am using UIBezierPath to have my imageview have round corners but I also want to add a border to the imageview. Keep in mind the top is a uiimage and the bottom is a label.
Currently using this code produces:
let rectShape = CAShapeLayer()
rectShape.bounds = myCell2.NewFeedImageView.frame
rectShape.position = myCell2.NewFeedImageView.center
rectShape.path = UIBezierPath(roundedRect: myCell2.NewFeedImageView.bounds,
byRoundingCorners: .TopRight | .TopLeft,
cornerRadii: CGSize(width: 25, height: 25)).CGPath
myCell2.NewFeedImageView.layer.mask = rectShape
I want to add a green border to that but I cant use
myCell2.NewFeedImageView.layer.borderWidth = 8
myCell2.NewFeedImageView.layer.borderColor = UIColor.greenColor().CGColor
because it cuts off the top left and top right corner of the border as seen in this image:
Is there a way too add in a border with UIBezierPath along with my current code?
You can reuse the UIBezierPath path and add a shape layer to the view. Here is an example inside a view controller.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Create a view with red background for demonstration
let v = UIView(frame: CGRectMake(0, 0, 100, 100))
v.center = view.center
v.backgroundColor = UIColor.redColor()
view.addSubview(v)
// Add rounded corners
let maskLayer = CAShapeLayer()
maskLayer.frame = v.bounds
maskLayer.path = UIBezierPath(roundedRect: v.bounds, byRoundingCorners: .TopRight | .TopLeft, cornerRadii: CGSize(width: 25, height: 25)).CGPath
v.layer.mask = maskLayer
// Add border
let borderLayer = CAShapeLayer()
borderLayer.path = maskLayer.path // Reuse the Bezier path
borderLayer.fillColor = UIColor.clearColor().CGColor
borderLayer.strokeColor = UIColor.greenColor().CGColor
borderLayer.lineWidth = 5
borderLayer.frame = v.bounds
v.layer.addSublayer(borderLayer)
}
}
The end result looks like this.
Note that this only works as expected when the view's size is fixed. When the view can resize, you will need to create a custom view class and resize the layers in layoutSubviews.
As it says above:
It is not easy to do this perfectly.
Here's a drop-in solution.
This
correctly addresses the issue that you are drawing HALF OF THE BORDER LINE
is totally usable in autolayout
completely re-works itself when the size of the view changes or animates
is totally IBDesignable - you can see it in realtime in your storyboard
for 2019 ...
#IBDesignable
class RoundedCornersAndTrueBorder: UIView {
#IBInspectable var cornerRadius: CGFloat = 10 {
didSet { setup() }
}
#IBInspectable var borderColor: UIColor = UIColor.black {
didSet { setup() }
}
#IBInspectable var trueBorderWidth: CGFloat = 2.0 {
didSet { setup() }
}
override func layoutSubviews() {
setup()
}
var border:CAShapeLayer? = nil
func setup() {
// make a path with round corners
let path = UIBezierPath(
roundedRect: self.bounds, cornerRadius:cornerRadius)
// note that it is >exactly< the size of the whole view
// mask the whole view to that shape
// note that you will ALSO be masking the border we'll draw below
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
// add another layer, which will be the border as such
if (border == nil) {
border = CAShapeLayer()
self.layer.addSublayer(border!)
}
// IN SOME APPROACHES YOU would INSET THE FRAME
// of the border-drawing layer by the width of the border
// border.frame = bounds.insetBy(dx: borderWidth, dy: borderWidth)
// so that when you draw the line, ALL of the WIDTH of the line
// DOES fall within the actual mask.
// here, we will draw the border-line LITERALLY ON THE EDGE
// of the path. that means >HALF< THE LINE will be INSIDE
// the path and HALF THE LINE WILL BE OUTSIDE the path
border!.frame = bounds
let pathUsingCorrectInsetIfAny =
UIBezierPath(roundedRect: border!.bounds, cornerRadius:cornerRadius)
border!.path = pathUsingCorrectInsetIfAny.cgPath
border!.fillColor = UIColor.clear.cgColor
// the following is not what you want:
// it results in "half-missing corners"
// (note however, sometimes you do use this approach):
//border.borderColor = borderColor.cgColor
//border.borderWidth = borderWidth
// this approach will indeed be "inside" the path:
border!.strokeColor = borderColor.cgColor
border!.lineWidth = trueBorderWidth * 2.0
// HALF THE LINE will be INSIDE the path and HALF THE LINE
// WILL BE OUTSIDE the path. so MAKE IT >>TWICE AS THICK<<
// as requested by the consumer class.
}
}
So that's it.
Beginner help for the question in the comments ...
Make a "new Swift file" called "Fattie.swift". (Note, funnily enough it actually makes no difference what you call it. If you are at the stage of "don't know how to make a new file" seek basic Xcode tutorials.)
Put all of the above code in the file
You've just added a class "RoundedCornersAndTrueBorder" to your project.
On your story board. Add an ordinary UIView to your scene. In fact, make it actually any size/shape whatsoever, anything you prefer.
Look at the Identity Inspector. (If you do not know what that is, seek basic tutorials.) Simply change the class to "RoundedCornersAndTrueBorder". (Once you start typing "Roun...", it will guess which class you mean.
You're done - run the project.
Note that you have to, of course, add complete and correct constraints to the UIView, just as with absolutely anything you do in Xcode. Enjoy!
Similar solutions:
https://stackoverflow.com/a/57465440/294884 - image + rounded + shadows
https://stackoverflow.com/a/41553784/294884 - two-corner problem
https://stackoverflow.com/a/59092828/294884 - "shadows + hole" or "glowbox" problem
https://stackoverflow.com/a/57400842/294884 - the "border AND gap" problem
https://stackoverflow.com/a/57514286/294884 - basic "adding" beziers
And please also see the alternate answer below! :)
Absolutely perfect 2019 solution
Without further ado, here's exactly how you do this.
Don't actually use the "basic" layer that comes with the view
Make a new layer only for the image. You can now mask this (circularly) without affecting the next layer
Make a new layer for the border as such. It will safely not be masked by the picture layer.
The key facts are
With a CALayer, you can indeed apply a .mask and it only affects that layer
When drawing a circle (or indeed any border), have to attend very carefully to the fact that you only get "half the width" - in short never crop using the same path you draw with.
Notice the original cat image is exactly as wide as the horizontal yellow arrow. You have to be careful to paint the image so that the whole image appears in the roundel, which is smaller than the overall custom control.
So, setup in the usual way
import UIKit
#IBDesignable class GreenCirclePerson: UIView {
#IBInspectable var borderColor: UIColor = UIColor.black { didSet { setup() } }
#IBInspectable var trueBorderThickness: CGFloat = 2.0 { didSet { setup() } }
#IBInspectable var trueGapThickness: CGFloat = 2.0 { didSet { setup() } }
#IBInspectable var picture: UIImage? = nil { didSet { setup() } }
override func layoutSubviews() { setup() }
var imageLayer: CALayer? = nil
var border: CAShapeLayer? = nil
func setup() {
if (imageLayer == nil) {
imageLayer = CALayer()
self.layer.addSublayer(imageLayer!)
}
if (border == nil) {
border = CAShapeLayer()
self.layer.addSublayer(border!)
}
Now carefully make the layer for the circularly-cropped image:
// the ultimate size of our custom control:
let box = self.bounds.aspectFit()
let totalInsetOnAnyOneSide = trueBorderThickness + trueGapThickness
let boxInWhichImageSits = box.inset(by:
UIEdgeInsets(top: totalInsetOnAnyOneSide, left: totalInsetOnAnyOneSide,
bottom: totalInsetOnAnyOneSide, right: totalInsetOnAnyOneSide))
// just a note. that version of inset#by is much clearer than the
// confusing dx/dy variant, so best to use that one
imageLayer!.frame = boxInWhichImageSits
imageLayer!.contents = picture?.cgImage
imageLayer?.contentsGravity = .resizeAspectFill
let halfImageSize = boxInWhichImageSits.width / 2.0
let maskPath = UIBezierPath(roundedRect: imageLayer!.bounds,
cornerRadius:halfImageSize)
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.cgPath
imageLayer!.mask = maskLayer
Next as a completely separate layer, draw the border as you wish:
// now create the border
border!.frame = bounds
// To draw the border, you must inset it by half the width of the border,
// otherwise you'll be drawing only half the border. (Indeed, as an additional
// subtle problem you are clipping rather than rendering the outside edge.)
let halfWidth = trueBorderThickness / 2.0
let borderCenterlineBox = box.inset(by:
UIEdgeInsets(top: halfWidth, left: halfWidth,
bottom: halfWidth, right: halfWidth))
let halfBorderBoxSize = borderCenterlineBox.width / 2.0
let borderPath = UIBezierPath(roundedRect: borderCenterlineBox,
cornerRadius:halfBorderBoxSize)
border!.path = borderPath.cgPath
border!.fillColor = UIColor.clear.cgColor
border!.strokeColor = borderColor.cgColor
border!.lineWidth = trueBorderThickness
}
}
Everything works perfectly as in iOS standard controls:
Everything which is invisible is invisible; you can see-through the overall custom control to any material behind, there are no "half thickness" problems or missing image material, you can set the custom control background color in the usual way, etc etc. The inspector controls all work properly. (Phew!)
Similar solutions:
https://stackoverflow.com/a/57465440/294884 - image + rounded + shadows
https://stackoverflow.com/a/41553784/294884 - two-corner problem
https://stackoverflow.com/a/59092828/294884 - "shadows + hole" or "glowbox" problem
https://stackoverflow.com/a/57400842/294884 - the "border AND gap" problem
https://stackoverflow.com/a/57514286/294884 - basic "adding" beziers
**use this extension for round borders and corner**
extension UIView {
func roundCorners(corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
func roundCornersWithBorder(corners: UIRectCorner, radius: CGFloat) {
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: radius, height: radius)).cgPath
layer.mask = maskLayer
// Add border
let borderLayer = CAShapeLayer()
borderLayer.path = maskLayer.path // Reuse the Bezier path
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor(red:3/255, green:33/255, blue:70/255, alpha: 0.15).cgColor
borderLayer.lineWidth = 2
borderLayer.frame = bounds
layer.addSublayer(borderLayer)
}
}
Use like this
myView.roundCornersWithBorder(corners: [.topLeft, .topRight], radius: 8.0)
myView.roundCorners(corners: [.topLeft, .topRight], radius: 8.0)
There sure is! Every view has a layer property (which you know from giving your layer rounded corners). Another two properties on layer are borderColor and borderWidth. Just by setting those you can add a border to your view! (The border will follow the rounded corners.) Be sure to use UIColor.CGColor for borderColor as a plain UIColor won't match the type.

How do you create a DropShadow on a UIView [duplicate]

I'm trying to draw a shadow under the bottom edge of a UIView in Cocoa Touch. I understand that I should use CGContextSetShadow() to draw the shadow, but the Quartz 2D programming guide is a little vague:
Save the graphics state.
Call the function CGContextSetShadow, passing the appropriate values.
Perform all the drawing to which you want to apply shadows.
Restore the graphics state
I've tried the following in a UIView subclass:
- (void)drawRect:(CGRect)rect {
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(currentContext);
CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
CGContextRestoreGState(currentContext);
[super drawRect: rect];
}
..but this doesn't work for me and I'm a bit stuck about (a) where to go next and (b) if there's anything I need to do to my UIView to make this work?
A by far easier approach is to set some layer attributes of the view on initialization:
self.layer.masksToBounds = NO;
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;
You need to import QuartzCore.
#import <QuartzCore/QuartzCore.h>
self.layer.masksToBounds = NO;
self.layer.cornerRadius = 8; // if you like rounded corners
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;
This will slow down the application.
Adding the following line can improve performance as long as your view is visibly rectangular:
self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
Same solution, but just to remind you: You can define the shadow directly in the storyboard.
Ex:
In your current code, you save the GState of the current context, configure it to draw a shadow .. and the restore it to what it was before you configured it to draw a shadow. Then, finally, you invoke the superclass's implementation of drawRect: .
Any drawing that should be affected by the shadow setting needs to happen after
CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
but before
CGContextRestoreGState(currentContext);
So if you want the superclass's drawRect: to be 'wrapped' in a shadow, then how about if you rearrange your code like this?
- (void)drawRect:(CGRect)rect {
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(currentContext);
CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
[super drawRect: rect];
CGContextRestoreGState(currentContext);
}
You can try this .... you can play with the values.
The shadowRadius dictates the amount of blur. shadowOffset dictates where the shadow goes.
Swift 2.0
let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height
demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4) //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds = false
demoView.layer.shadowPath = shadowPath.CGPath
Swift 3.0
let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height
demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.black.cgColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4) //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds = false
demoView.layer.shadowPath = shadowPath.cgPath
Example with spread
To create a basic shadow
demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSizeMake(0.5, 4.0); //Here your control your spread
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
Basic Shadow example in Swift 2.0
Simple and clean solution using Interface Builder
Add a file named UIView.swift in your project (or just paste this in any file) :
import UIKit
#IBDesignable extension UIView {
/* The color of the shadow. Defaults to opaque black. Colors created
* from patterns are currently NOT supported. Animatable. */
#IBInspectable var shadowColor: UIColor? {
set {
layer.shadowColor = newValue!.CGColor
}
get {
if let color = layer.shadowColor {
return UIColor(CGColor:color)
}
else {
return nil
}
}
}
/* The opacity of the shadow. Defaults to 0. Specifying a value outside the
* [0,1] range will give undefined results. Animatable. */
#IBInspectable var shadowOpacity: Float {
set {
layer.shadowOpacity = newValue
}
get {
return layer.shadowOpacity
}
}
/* The shadow offset. Defaults to (0, -3). Animatable. */
#IBInspectable var shadowOffset: CGPoint {
set {
layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
}
get {
return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
}
}
/* The blur radius used to create the shadow. Defaults to 3. Animatable. */
#IBInspectable var shadowRadius: CGFloat {
set {
layer.shadowRadius = newValue
}
get {
return layer.shadowRadius
}
}
}
Then this will be available in Interface Builder for every view in the Utilities Panel > Attributes Inspector :
You can easily set the shadow now.
Notes:
- The shadow won't appear in IB, only at runtime.
- As Mazen Kasser said
To those who failed in getting this to work [...] make sure Clip Subviews (clipsToBounds) is not enabled
I use this as part of my utils. With this we can not only set shadow but also can get a rounded corner for any UIView. Also you could set what color shadow you prefer. Normally black is preferred but sometimes, when the background is non-white you might want something else. Here's what I use -
in utils.m
+ (void)roundedLayer:(CALayer *)viewLayer
radius:(float)r
shadow:(BOOL)s
{
[viewLayer setMasksToBounds:YES];
[viewLayer setCornerRadius:r];
[viewLayer setBorderColor:[RGB(180, 180, 180) CGColor]];
[viewLayer setBorderWidth:1.0f];
if(s)
{
[viewLayer setShadowColor:[RGB(0, 0, 0) CGColor]];
[viewLayer setShadowOffset:CGSizeMake(0, 0)];
[viewLayer setShadowOpacity:1];
[viewLayer setShadowRadius:2.0];
}
return;
}
To use this we need to call this - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];
Swift 3
extension UIView {
func installShadow() {
layer.cornerRadius = 2
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 1)
layer.shadowOpacity = 0.45
layer.shadowPath = UIBezierPath(rect: bounds).cgPath
layer.shadowRadius = 1.0
}
}
If you would like to use StoryBoard and wouldnt like to keep typing in runtime attributes, you can easily create an extension to views and make them usable in storyboard.
Step 1. create extension
extension UIView {
#IBInspectable var shadowRadius: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowRadius = newValue
}
}
#IBInspectable var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
#IBInspectable var shadowOffset: CGSize {
get {
return layer.shadowOffset
}
set {
layer.shadowOffset = newValue
}
}
#IBInspectable var maskToBound: Bool {
get {
return layer.masksToBounds
}
set {
layer.masksToBounds = newValue
}
}
}
step 2. you can now use these attributes in storyboard
To those who failed in getting this to work (As myself!) after trying all the answers here, just make sure Clip Subviews is not enabled at the Attributes inspector...
Sketch Shadow Using IBDesignable and IBInspectable in Swift 4
HOW TO USE IT
SKETCH AND XCODE SIDE BY SIDE
CODE
#IBDesignable class ShadowView: UIView {
#IBInspectable var shadowColor: UIColor? {
get {
if let color = layer.shadowColor {
return UIColor(cgColor: color)
}
return nil
}
set {
if let color = newValue {
layer.shadowColor = color.cgColor
} else {
layer.shadowColor = nil
}
}
}
#IBInspectable var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
#IBInspectable var shadowOffset: CGPoint {
get {
return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
}
set {
layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
}
}
#IBInspectable var shadowBlur: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowRadius = newValue / 2.0
}
}
#IBInspectable var shadowSpread: CGFloat = 0 {
didSet {
if shadowSpread == 0 {
layer.shadowPath = nil
} else {
let dx = -shadowSpread
let rect = bounds.insetBy(dx: dx, dy: dx)
layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
}
}
}
OUTPUT
You can use my utility function created for shadow and corner radius as below:
- (void)addShadowWithRadius:(CGFloat)shadowRadius withShadowOpacity:(CGFloat)shadowOpacity withShadowOffset:(CGSize)shadowOffset withShadowColor:(UIColor *)shadowColor withCornerRadius:(CGFloat)cornerRadius withBorderColor:(UIColor *)borderColor withBorderWidth:(CGFloat)borderWidth forView:(UIView *)view{
// drop shadow
[view.layer setShadowRadius:shadowRadius];
[view.layer setShadowOpacity:shadowOpacity];
[view.layer setShadowOffset:shadowOffset];
[view.layer setShadowColor:shadowColor.CGColor];
// border radius
[view.layer setCornerRadius:cornerRadius];
// border
[view.layer setBorderColor:borderColor.CGColor];
[view.layer setBorderWidth:borderWidth];
}
Hope it will help you!!!
Swift 3
self.paddingView.layer.masksToBounds = false
self.paddingView.layer.shadowOffset = CGSize(width: -15, height: 10)
self.paddingView.layer.shadowRadius = 5
self.paddingView.layer.shadowOpacity = 0.5
All Answer all well but I want to add one more point
If you encounter a problem when you have table cells, Deque a new cell there is a mismatch in shadow so in this case, you need to place your shadow code in a layoutSubviews method so that it will behave nicely in all conditions.
-(void)layoutSubviews{
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
[VPShadow applyShadowView:self];
}
or in ViewControllers for specific view place shadow code inside the following method so that it's work well
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
[self.viewShadow layoutIfNeeded];
[VPShadow applyShadowView:self.viewShadow];
}
I have modified my shadow implementation for new devs for more generalized form ex:
/*!
#brief Add shadow to a view.
#param layer CALayer of the view.
*/
+(void)applyShadowOnView:(CALayer *)layer OffsetX:(CGFloat)x OffsetY:(CGFloat)y blur:(CGFloat)radius opacity:(CGFloat)alpha RoundingCorners:(CGFloat)cornerRadius{
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:cornerRadius];
layer.masksToBounds = NO;
layer.shadowColor = [UIColor blackColor].CGColor;
layer.shadowOffset = CGSizeMake(x,y);// shadow x and y
layer.shadowOpacity = alpha;
layer.shadowRadius = radius;// blur effect
layer.shadowPath = shadowPath.CGPath;
}
For fellow Xamarians, the Xamarin.iOS/C# version of the answer would look like the following:
public override void DrawRect(CGRect area, UIViewPrintFormatter formatter)
{
CGContext currentContext = UIGraphics.GetCurrentContext();
currentContext.SaveState();
currentContext.SetShadow(new CGSize(-15, 20), 5);
base.DrawRect(area, formatter);
currentContext.RestoreState();
}
The main difference is that you acquire an instance of CGContext on which you directly call the appropriate methods.
You can use this Extension to add shadow
extension UIView {
func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float)
{
layer.masksToBounds = false
layer.shadowOffset = offset
layer.shadowColor = color.cgColor
layer.shadowRadius = radius
layer.shadowOpacity = opacity
let backgroundCGColor = backgroundColor?.cgColor
backgroundColor = nil
layer.backgroundColor = backgroundCGColor
}
}
you can call it like
your_Custom_View.addShadow(offset: CGSize(width: 0, height: 1), color: UIColor.black, radius: 2.0, opacity: 1.0)

How do I draw a shadow under a UIView?

I'm trying to draw a shadow under the bottom edge of a UIView in Cocoa Touch. I understand that I should use CGContextSetShadow() to draw the shadow, but the Quartz 2D programming guide is a little vague:
Save the graphics state.
Call the function CGContextSetShadow, passing the appropriate values.
Perform all the drawing to which you want to apply shadows.
Restore the graphics state
I've tried the following in a UIView subclass:
- (void)drawRect:(CGRect)rect {
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(currentContext);
CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
CGContextRestoreGState(currentContext);
[super drawRect: rect];
}
..but this doesn't work for me and I'm a bit stuck about (a) where to go next and (b) if there's anything I need to do to my UIView to make this work?
A by far easier approach is to set some layer attributes of the view on initialization:
self.layer.masksToBounds = NO;
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;
You need to import QuartzCore.
#import <QuartzCore/QuartzCore.h>
self.layer.masksToBounds = NO;
self.layer.cornerRadius = 8; // if you like rounded corners
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;
This will slow down the application.
Adding the following line can improve performance as long as your view is visibly rectangular:
self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
Same solution, but just to remind you: You can define the shadow directly in the storyboard.
Ex:
In your current code, you save the GState of the current context, configure it to draw a shadow .. and the restore it to what it was before you configured it to draw a shadow. Then, finally, you invoke the superclass's implementation of drawRect: .
Any drawing that should be affected by the shadow setting needs to happen after
CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
but before
CGContextRestoreGState(currentContext);
So if you want the superclass's drawRect: to be 'wrapped' in a shadow, then how about if you rearrange your code like this?
- (void)drawRect:(CGRect)rect {
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(currentContext);
CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
[super drawRect: rect];
CGContextRestoreGState(currentContext);
}
You can try this .... you can play with the values.
The shadowRadius dictates the amount of blur. shadowOffset dictates where the shadow goes.
Swift 2.0
let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height
demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4) //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds = false
demoView.layer.shadowPath = shadowPath.CGPath
Swift 3.0
let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height
demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.black.cgColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4) //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds = false
demoView.layer.shadowPath = shadowPath.cgPath
Example with spread
To create a basic shadow
demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSizeMake(0.5, 4.0); //Here your control your spread
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
Basic Shadow example in Swift 2.0
Simple and clean solution using Interface Builder
Add a file named UIView.swift in your project (or just paste this in any file) :
import UIKit
#IBDesignable extension UIView {
/* The color of the shadow. Defaults to opaque black. Colors created
* from patterns are currently NOT supported. Animatable. */
#IBInspectable var shadowColor: UIColor? {
set {
layer.shadowColor = newValue!.CGColor
}
get {
if let color = layer.shadowColor {
return UIColor(CGColor:color)
}
else {
return nil
}
}
}
/* The opacity of the shadow. Defaults to 0. Specifying a value outside the
* [0,1] range will give undefined results. Animatable. */
#IBInspectable var shadowOpacity: Float {
set {
layer.shadowOpacity = newValue
}
get {
return layer.shadowOpacity
}
}
/* The shadow offset. Defaults to (0, -3). Animatable. */
#IBInspectable var shadowOffset: CGPoint {
set {
layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
}
get {
return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
}
}
/* The blur radius used to create the shadow. Defaults to 3. Animatable. */
#IBInspectable var shadowRadius: CGFloat {
set {
layer.shadowRadius = newValue
}
get {
return layer.shadowRadius
}
}
}
Then this will be available in Interface Builder for every view in the Utilities Panel > Attributes Inspector :
You can easily set the shadow now.
Notes:
- The shadow won't appear in IB, only at runtime.
- As Mazen Kasser said
To those who failed in getting this to work [...] make sure Clip Subviews (clipsToBounds) is not enabled
I use this as part of my utils. With this we can not only set shadow but also can get a rounded corner for any UIView. Also you could set what color shadow you prefer. Normally black is preferred but sometimes, when the background is non-white you might want something else. Here's what I use -
in utils.m
+ (void)roundedLayer:(CALayer *)viewLayer
radius:(float)r
shadow:(BOOL)s
{
[viewLayer setMasksToBounds:YES];
[viewLayer setCornerRadius:r];
[viewLayer setBorderColor:[RGB(180, 180, 180) CGColor]];
[viewLayer setBorderWidth:1.0f];
if(s)
{
[viewLayer setShadowColor:[RGB(0, 0, 0) CGColor]];
[viewLayer setShadowOffset:CGSizeMake(0, 0)];
[viewLayer setShadowOpacity:1];
[viewLayer setShadowRadius:2.0];
}
return;
}
To use this we need to call this - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];
Swift 3
extension UIView {
func installShadow() {
layer.cornerRadius = 2
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 1)
layer.shadowOpacity = 0.45
layer.shadowPath = UIBezierPath(rect: bounds).cgPath
layer.shadowRadius = 1.0
}
}
If you would like to use StoryBoard and wouldnt like to keep typing in runtime attributes, you can easily create an extension to views and make them usable in storyboard.
Step 1. create extension
extension UIView {
#IBInspectable var shadowRadius: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowRadius = newValue
}
}
#IBInspectable var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
#IBInspectable var shadowOffset: CGSize {
get {
return layer.shadowOffset
}
set {
layer.shadowOffset = newValue
}
}
#IBInspectable var maskToBound: Bool {
get {
return layer.masksToBounds
}
set {
layer.masksToBounds = newValue
}
}
}
step 2. you can now use these attributes in storyboard
To those who failed in getting this to work (As myself!) after trying all the answers here, just make sure Clip Subviews is not enabled at the Attributes inspector...
Sketch Shadow Using IBDesignable and IBInspectable in Swift 4
HOW TO USE IT
SKETCH AND XCODE SIDE BY SIDE
CODE
#IBDesignable class ShadowView: UIView {
#IBInspectable var shadowColor: UIColor? {
get {
if let color = layer.shadowColor {
return UIColor(cgColor: color)
}
return nil
}
set {
if let color = newValue {
layer.shadowColor = color.cgColor
} else {
layer.shadowColor = nil
}
}
}
#IBInspectable var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
#IBInspectable var shadowOffset: CGPoint {
get {
return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
}
set {
layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
}
}
#IBInspectable var shadowBlur: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowRadius = newValue / 2.0
}
}
#IBInspectable var shadowSpread: CGFloat = 0 {
didSet {
if shadowSpread == 0 {
layer.shadowPath = nil
} else {
let dx = -shadowSpread
let rect = bounds.insetBy(dx: dx, dy: dx)
layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
}
}
}
OUTPUT
You can use my utility function created for shadow and corner radius as below:
- (void)addShadowWithRadius:(CGFloat)shadowRadius withShadowOpacity:(CGFloat)shadowOpacity withShadowOffset:(CGSize)shadowOffset withShadowColor:(UIColor *)shadowColor withCornerRadius:(CGFloat)cornerRadius withBorderColor:(UIColor *)borderColor withBorderWidth:(CGFloat)borderWidth forView:(UIView *)view{
// drop shadow
[view.layer setShadowRadius:shadowRadius];
[view.layer setShadowOpacity:shadowOpacity];
[view.layer setShadowOffset:shadowOffset];
[view.layer setShadowColor:shadowColor.CGColor];
// border radius
[view.layer setCornerRadius:cornerRadius];
// border
[view.layer setBorderColor:borderColor.CGColor];
[view.layer setBorderWidth:borderWidth];
}
Hope it will help you!!!
Swift 3
self.paddingView.layer.masksToBounds = false
self.paddingView.layer.shadowOffset = CGSize(width: -15, height: 10)
self.paddingView.layer.shadowRadius = 5
self.paddingView.layer.shadowOpacity = 0.5
All Answer all well but I want to add one more point
If you encounter a problem when you have table cells, Deque a new cell there is a mismatch in shadow so in this case, you need to place your shadow code in a layoutSubviews method so that it will behave nicely in all conditions.
-(void)layoutSubviews{
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
[VPShadow applyShadowView:self];
}
or in ViewControllers for specific view place shadow code inside the following method so that it's work well
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
[self.viewShadow layoutIfNeeded];
[VPShadow applyShadowView:self.viewShadow];
}
I have modified my shadow implementation for new devs for more generalized form ex:
/*!
#brief Add shadow to a view.
#param layer CALayer of the view.
*/
+(void)applyShadowOnView:(CALayer *)layer OffsetX:(CGFloat)x OffsetY:(CGFloat)y blur:(CGFloat)radius opacity:(CGFloat)alpha RoundingCorners:(CGFloat)cornerRadius{
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:cornerRadius];
layer.masksToBounds = NO;
layer.shadowColor = [UIColor blackColor].CGColor;
layer.shadowOffset = CGSizeMake(x,y);// shadow x and y
layer.shadowOpacity = alpha;
layer.shadowRadius = radius;// blur effect
layer.shadowPath = shadowPath.CGPath;
}
For fellow Xamarians, the Xamarin.iOS/C# version of the answer would look like the following:
public override void DrawRect(CGRect area, UIViewPrintFormatter formatter)
{
CGContext currentContext = UIGraphics.GetCurrentContext();
currentContext.SaveState();
currentContext.SetShadow(new CGSize(-15, 20), 5);
base.DrawRect(area, formatter);
currentContext.RestoreState();
}
The main difference is that you acquire an instance of CGContext on which you directly call the appropriate methods.
You can use this Extension to add shadow
extension UIView {
func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float)
{
layer.masksToBounds = false
layer.shadowOffset = offset
layer.shadowColor = color.cgColor
layer.shadowRadius = radius
layer.shadowOpacity = opacity
let backgroundCGColor = backgroundColor?.cgColor
backgroundColor = nil
layer.backgroundColor = backgroundCGColor
}
}
you can call it like
your_Custom_View.addShadow(offset: CGSize(width: 0, height: 1), color: UIColor.black, radius: 2.0, opacity: 1.0)