In a custom tableview cell, I'm drawing a simple rectangle with a shadow like this:
photoBorder = [[[UIView alloc] initWithFrame:CGRectMake(4, 4, self.frame.size.width-8, 190)] autorelease];
photoBorder.autoresizingMask = UIViewAutoresizingFlexibleWidth;
photoBorder.backgroundColor = [UIColor whiteColor];
photoBorder.layer.masksToBounds = NO;
photoBorder.layer.shadowOffset = CGSizeMake(0, 1);
photoBorder.layer.shadowRadius = 4;
photoBorder.layer.shadowOpacity = 1.0;
photoBorder.layer.shadowColor = [UIColor darkGrayColor].CGColor;
photoBorder.layer.shouldRasterize = YES;
photoBorder.layer.shadowPath = [UIBezierPath bezierPathWithRect:photoBorder.bounds].CGPath; // this line seems to be causing the problem
This works fine when the view first loads. However, when you rotate the device, the shadow stays the same size. I'd really like it to stretch to the new width of "photoBorder".
I can get it to work by removing the shadowPath, but the tableview takes a noticeable performance hit.
Anyone have any tips on making a shadow, on a UIView, that can stretch, without losing performance?
After searching for a few hours and not finding anything, I posted this. Then found an answer a few minutes later.
The simple solution for me appears to be simply moving the shadowPath into layoutSubviews.
- (void)layoutSubviews{
photoBorder.layer.shadowPath = [UIBezierPath bezierPathWithRect:photoBorder.bounds].CGPath;
}
You need to create a subclass of UIView so that you can get the new bounds in the layoutSubviews() method.
Note: If you try to add this code in a ViewController that owns the subview, the bounds will remain static as you rotate, which results in the wrong shadowPath.
import UIKit
class BackgroundView: UIView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
updateShadow(on: self)
}
func updateShadow(on background: UIView) {
let layer = background.layer
layer.shadowPath = UIBezierPath(rect: background.bounds).cgPath
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 2)
layer.shadowRadius = 4
layer.shadowOpacity = 0.22
}
}
Make sure you call super.layoutSubviews() to handle any Auto Layout constraints.
You can set a custom class in a Storyboard file.
for performance enhancement you can draw an inner shadow using Core Graphics.
Inner shadow effect on UIView layer
Related
I am using IGListKit with my UICollectionView. For now the CollectionView is pretty simple, as it only have one cell per section. That cell contains an inner horizontal UICollectionView as an image slideshow.
As I need some shadowing around my entire sections, I am using Decoration Views, and apply it a border shadow: layer.shadowPath
I noticed something weird, the shadow's opacity changes upon the picture currently displayed in the slideshow. If the picture (or a portion of the picture) is bright, you can see the shadow opacity changing.
I don't know if it is something I can fix.
You can clearly see that if I take a screenshot while swiping pictures in the slideshow, the shadow on the top is darker on one side.
Code for decoration view:
class FeedItemBackgroundShadowView: UICollectionReusableView {
// MARK: Initialization
... Constructors calling setup
// MARK: Setup
override func layoutSubviews() {
super.layoutSubviews()
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: Constants.cornerRadius).cgPath
}
func setup() {
self.layer.cornerRadius = 12.0
self.layer.backgroundColor = UIColor.white.cgColor
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0.0, height: 2.5)
self.layer.shadowRadius = 12.0
self.layer.shadowOpacity = 0.35
}
Rest of the code a simply UICollectionViewCells that embed a UICollectionView
Are you certain it isn't an optical illusion?
I would like to create custom ContainerView with shadowed and rounded edges. This ContainerView is in form of small rectangle placed on the top of another UIView. In this peculiar situation neither additional layers nor drawing shadow using CoreGraphics are helpful.
You're wrong that additional views/layers won't help.
You can place roundedContainer with rounded corners into another shadowedView with shadow added to it's layer.
To avoid those white corners make sure you set background color to clear somewhere.
Example:
//superview for container with rounded corners
shadowedView.backgroundColor = UIColor.clear //this will fix your white corners issue
shadowedView.layer.shadowColor = UIColor.black.cgColor
shadowedView.layer.shadowOffset = .zero
shadowedView.layer.shadowOpacity = 0.3
shadowedView.layer.shadowRadius = 5.0
//add a container with rounded corners
let roundedView = UIView()
roundedView.frame = baseView.bounds
roundedView.layer.cornerRadius = 10
roundedView.layer.masksToBounds = true
shadowedView.addSubview(roundedView)
I found a proper solution. I dropped shadow to ContainerView which is a superclass for every UIView inside. Then, I rounded edges using UIViewController class for this small rectangle area.
class GraphViewController: UIViewController {
#IBOutlet var graphView: GraphViewRenderer!
override func viewDidLoad() {
graphView.layer.cornerRadius = 20.0
graphView.layer.masksToBounds = true
super.viewDidLoad()
}
}
class GraphContainerView: UIView {
func applyPlainShadow() {
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize.zero
layer.shadowRadius = 5.0
layer.shadowOpacity = 0.7
}
override init(frame: CGRect) {
super.init(frame: frame)
applyPlainShadow()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
applyPlainShadow()
}
}
How do I turn a rectangular image view into a circular image view that can hold shape in auto layout without setting width and height restraints? Thereby allowing the imageView to define it’s size, and size bigger and smaller relative to objects around it with leading, trailing, top, and bottom constraints.
I asked a similar question the other day, but I think this might be posed in a more concise way. Thanks so much!
EDIT
Ok, I started over to make this as simple as possible. I have a view named "Cell" and a UIImageView named "dog" within the cell, and that's it. I don't have "unable to simultaneously satisfy constraints" in the console anymore, just two simple views using auto layout. I'm still trying to use this code to round the UIImageView:
profileImageView.layer.cornerRadius = profileImageView.frame.size.width / 2
profileImageView.clipsToBounds = true
Here is the cell constraint setup:
Here is the profile pic constraint setup:
Here is the result without the code, no rounding, but nice and square:
Here is the result with the code to round:
This makes no sense to me, because without the rounding code the image is square, and with the code it's diamond shaped. If it's square shouldn't it be a circle with no issues?
EDIT 2
Here's what happens when I remove the bottom constraint and add a multiplier of .637 for equal height to superview.
Unfortunately you cannot do this using cornerRadius and autolayout — the CGLayer is not affected by autolayout, so any change in the size of the view will not change the radius which has been set once causing, as you have noticed, the circle to lose its shape.
You can create a custom subclass of UIImageView and override layoutSubviews in order to set the cornerRadius each time the bounds of the imageview change.
EDIT
An example might look something like this:
class Foo: UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
let radius: CGFloat = self.bounds.size.width / 2.0
self.layer.cornerRadius = radius
}
}
And obviously you would have to constrain the Foobar instance's width to be the same as the height (to maintain a circle). You would probably also want to set the Foobar instance's contentMode to UIViewContentMode.ScaleAspectFill so that it knows how to draw the image (this means that the image is likely to be cropped).
Setting radius in viewWillLayoutSubviews will solve the problem
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
profileImageView.layer.cornerRadius = profileImageView.frame.height / 2.0
}
create new interface in your .h file like
#interface CornerClip : UIImageView
#end
and implementation in .m file like
#implementation cornerClip
-(void)layoutSubviews
{
[super layoutSubviews];
CGFloat radius = self.bounds.size.width / 2.0;
self.layer.cornerRadius = radius;
}
#end
now just give class as "CornerClip" to your imageview.
100% working... Enjoy
First of all, I should mention that u can get a circle shape for your UIView/UIImageView only if the width and height will be equal. It's important to understand. In all other cases (when width != height), you won't get a circle shape because the initial shape of your UI instance was a rectangle.
OK, with this so UIKit SDK provides for developers a mechanism to manipulate the UIview's layer instance to change somehow any of layer's parameters, including setting up a mask to replace the initial shape of UIView element with the custom one. Those instruments are IBDesignable/IBInspectable. The goal is to preview our custom views directly through Interface Builder.
So using those keywords we can write our custom class, which will deal only with the single condition whether we need to round corners for our UI element or not.
For example, let's create the class extended from the UIImageView.
#IBDesignable
class UIRoundedImageView: UIImageView {
#IBInspectable var isRoundedCorners: Bool = false {
didSet { setNeedsLayout() }
}
override func layoutSubviews() {
super.layoutSubviews()
if isRoundedCorners {
let shapeLayer = CAShapeLayer()
shapeLayer.path = UIBezierPath(ovalIn:
CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.width, height: bounds.height
)).cgPath
layer.mask = shapeLayer
}
else {
layer.mask = nil
}
}
}
After setting the class name for your UIImageView element (where the dog picture is), in your storyboard, you will get a new option, appeared in the Attributes Inspector menu (details at the screenshot).
The final result should be like this one.
It seems when you add one view as a subview of another that netted view will not necessarily have the same height as its superview. That's what the problem seems like. The solution is to not add your imageView as a subview, but have it on top of your backgroundView. In the image below I'm using a UILabel as my backgroundView.
Also in your case, when you're setting the cornerRadius use this: let radius: CGFloat = self.bounds.size.height / 2.0.
With my hacky solution you'll get smooth corner radius animation alongside frame size change.
Let's say you have ViewSubclass : UIView. It should contain the following code:
class ViewSubclass: UIView {
var animationDuration : TimeInterval?
let imageView = UIImageView()
//some imageView setup code
override func layoutSubviews() {
super.layoutSubviews()
if let duration = animationDuration {
let anim = CABasicAnimation(keyPath: "cornerRadius")
anim.fromValue = self.imageView.cornerRadius
let radius = self.imageView.frame.size.width / 2
anim.toValue = radius
anim.duration = duration
self.imageView.layer.cornerRadius = radius
self.imageView.layer.add(anim, forKey: "cornerRadius")
} else {
imageView.cornerRadius = imageView.frame.size.width / 2
}
animationDuration = nil
}
}
An then you'll have to do this:
let duration = 0.4 //For example
instanceOfViewSubclass.animationDuration = duration
UIView.animate(withDuration: duration, animations: {
//Your animation
instanceOfViewSubclass.layoutIfNeeded()
})
It's not beautiful, and might not work for complex multi-animations, but does answer the question.
Swift 4+ clean solution based on omaralbeik's answer
import UIKit
extension UIImageView {
func setRounded(borderWidth: CGFloat = 0.0, borderColor: UIColor = UIColor.clear) {
layer.cornerRadius = frame.width / 2
layer.masksToBounds = true
layer.borderWidth = borderWidth
layer.borderColor = borderColor.cgColor
}
}
Sample usage in UIViewController
1.Simply rounded UIImageView
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
imageView.setRounded()
}
2.Rounded UIImageView with border width and color
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
imageView.setRounded(borderWidth: 1.0, borderColor: UIColor.red)
}
write this code
override viewDidLayoutSubViews() {
profileImageView.layer.cornerRadius = profileImageView.frame.size.width / 2
profileImageView.clipsToBounds = true
}
in this case it will called after calculating the autolayout calculations in the first code you called the cornerradius code before calculating the actual size of the view cuz it's dynamically calculated using aspect ratio , the actual corner radius is calculated before equaling the width and the height of the view
I have same problem, and Now I get what happened here, hope some ideas can help you:
there are something different between the tows:
your profileImageView in storyboard
your profileImageView in viewDidLoad
the size of bound and frame is different when viewDidLoad and in storyboard,just because view is resize for different device size.
You can try it print(profileImageView.bounds.size) in viewDidLoad and viewDidAppear you will find the size in viewDidLoad you set cornerRadius is not the real "running" size.
a tips for you:
you can use a subClass of ImageView to avoid it, or do not use it in storyboard,
If you have subclassed the UIImageView. Then just add this piece of magical code in it.
Written in : Swift 3
override func layoutSubviews() {
super.layoutSubviews()
if self.isCircular! {
self.layer.cornerRadius = self.bounds.size.width * 0.50
}
}
I am quite new to iOS native development, but I had the same problem and found a solution.
So the green background has this constraints:
backgroundView.translatesAutoresizingMaskIntoConstraints = false
backgroundView.topAnchor.constraint(equalTo: superview!.safeAreaLayoutGuide.topAnchor).isActive = true
backgroundView.leftAnchor.constraint(equalTo: superview!.leftAnchor).isActive = true
backgroundView.widthAnchor.constraint(equalTo: superview!.widthAnchor).isActive = true
backgroundView.heightAnchor.constraint(equalTo: superview!.heightAnchor, multiplier: 0.2).isActive = true
The image constraints:
avatar.translatesAutoresizingMaskIntoConstraints = false
avatar.heightAnchor.constraint(equalTo: backgroundView.heightAnchor, multiplier: 0.8).isActive = true
avatar.widthAnchor.constraint(equalTo: backgroundView.heightAnchor, multiplier: 0.8).isActive = true
avatar.centerYAnchor.constraint(equalTo: backgroundView.centerYAnchor, constant: 0).isActive = true
avatar.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor, constant: 20).isActive = true
on viewWillLayoutSubviews() method I set the corner radius
to
avatar.layer.cornerRadius = (self.frame.height * 0.2 * 0.8) / 2
Basically, I am simply calculating the height of the image and then divide it by 2. 0.2 is the backgroungView height constraint multiplier and 0.8 the image width/height constraint multiplier.
Solution: Crop the image
[imageView setImage:[[imageView image] imageWithRoundedCorners:imageView.image.size.width/2]];
imageView.contentMode = UIViewContentModeScaleAspectFill;
I was looking for the same solution for profile pictures. After some hit and try and going through available functions, I came across something which works and is a nice way to ensure its safe. You can use the following function to crop out a round image from the original image and then you need not worry about the corner radius.
Post this even if your view size changes the image remains round and looks good.
Add a 1:1 aspect ratio constraint to the imageView for it to remain circular, despite any height or width changes.
I added custom IBInspectable cornerRadiusPercent, so you can do it without any code.
class RoundButton: UIButton {
override var bounds: CGRect {
didSet {
updateCornerRadius()
}
}
//private var cornerRadiusWatcher : CornerRadiusPercent?
#IBInspectable var cornerRadiusPercent: CGFloat = 0 {
didSet {
updateCornerRadius()
}
}
func updateCornerRadius()
{
layer.cornerRadius = bounds.height * cornerRadiusPercent
}
}
Can be easily done by creating an IBOutlet for the constraint which needs to be changed at runtime. Below is the code:
Create a IBOutlet for the constraint that needs to be changed at run time.
#IBOutlet var widthConstraint: NSLayoutConstraint!
Add below code in viewDidLoad():
self.widthConstraint.constant = imageWidthConstraintConstant()
Below function determines for device types change the width constraint accordingly.
func imageWidthConstraintConstant() -> CGFloat {
switch(self.screenWidth()) {
case 375:
return 100
case 414:
return 120
case 320:
return 77
default:
return 77
}
}
I am trying to add a drop shadow to views that are layered on top of one another, the views collapse allowing content in other views to be seen, in this vein i want to keep view.clipsToBounds ON so that when the views collapse their content is clipped.
This seems to have made it difficult for me to add a drop shadow to the layers as when i turn clipsToBounds ON the shadows are clipped also.
I have been trying to manipulate view.frame and view.bounds in order to add a drop shadow to the frame but allow the bounds to be large enough to encompass it, however I have had no luck with this.
Here is the code I am using to add a Shadow (this only works with clipsToBounds OFF as shown)
view.clipsToBounds = NO;
view.layer.shadowColor = [[UIColor blackColor] CGColor];
view.layer.shadowOffset = CGSizeMake(0,5);
view.layer.shadowOpacity = 0.5;
Here is a screenshot of the shadow being applied to the top lightest grey layer. Hopefully this gives an idea of how my content will overlap if clipsToBounds is OFF.
How can I add a shadow to my UIView and keep my content clipped?
Edit: Just wanted to add that I have also played around with using background images with shadows on, which does work well, however I would still like to know the best coded solution for this.
Try this:
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:view.bounds];
view.layer.masksToBounds = NO;
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = shadowPath.CGPath;
First of all: The UIBezierPath used as shadowPath is crucial. If you don't use it, you might not notice a difference at first, but the keen eye will observe a certain lag occurring during events like rotating the device and/or similar. It's an important performance tweak.
Regarding your issue specifically: The important line is view.layer.masksToBounds = NO. It disables the clipping of the view's layer's sublayers that extend further than the view's bounds.
For those wondering what the difference between masksToBounds (on the layer) and the view's own clipToBounds property is: There isn't really any. Toggling one will have an effect on the other. Just a different level of abstraction.
Swift 2.2:
override func layoutSubviews()
{
super.layoutSubviews()
let shadowPath = UIBezierPath(rect: bounds)
layer.masksToBounds = false
layer.shadowColor = UIColor.blackColor().CGColor
layer.shadowOffset = CGSizeMake(0.0, 5.0)
layer.shadowOpacity = 0.5
layer.shadowPath = shadowPath.CGPath
}
Swift 3:
override func layoutSubviews()
{
super.layoutSubviews()
let shadowPath = UIBezierPath(rect: bounds)
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
layer.shadowOpacity = 0.5
layer.shadowPath = shadowPath.cgPath
}
Wasabii's answer in Swift 2.3:
let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.blackColor().CGColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.CGPath
And in Swift 3/4/5:
let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.cgPath
Put this code in layoutSubviews() if you're using AutoLayout.
In SwiftUI, this is all much easier:
Color.yellow // or whatever your view
.shadow(radius: 3)
.frame(width: 200, height: 100)
The trick is defining the masksToBounds property of your view's layer properly:
view.layer.masksToBounds = NO;
and it should work.
(Source)
You can create an extension for UIView to access these values in the design editor
extension UIView{
#IBInspectable var shadowOffset: CGSize{
get{
return self.layer.shadowOffset
}
set{
self.layer.shadowOffset = newValue
}
}
#IBInspectable var shadowColor: UIColor{
get{
return UIColor(cgColor: self.layer.shadowColor!)
}
set{
self.layer.shadowColor = newValue.cgColor
}
}
#IBInspectable var shadowRadius: CGFloat{
get{
return self.layer.shadowRadius
}
set{
self.layer.shadowRadius = newValue
}
}
#IBInspectable var shadowOpacity: Float{
get{
return self.layer.shadowOpacity
}
set{
self.layer.shadowOpacity = newValue
}
}
}
You can set shadow to your view from storyboard also
On viewWillLayoutSubviews:
override func viewWillLayoutSubviews() {
sampleView.layer.masksToBounds = false
sampleView.layer.shadowColor = UIColor.darkGrayColor().CGColor;
sampleView.layer.shadowOffset = CGSizeMake(2.0, 2.0)
sampleView.layer.shadowOpacity = 1.0
}
Using Extension of UIView:
extension UIView {
func addDropShadowToView(targetView:UIView? ){
targetView!.layer.masksToBounds = false
targetView!.layer.shadowColor = UIColor.darkGrayColor().CGColor;
targetView!.layer.shadowOffset = CGSizeMake(2.0, 2.0)
targetView!.layer.shadowOpacity = 1.0
}
}
Usage:
sampleView.addDropShadowToView(sampleView)
So yes, you should prefer the shadowPath property for performance, but also:
From the header file of CALayer.shadowPath
Specifying the path explicitly using this property will usually
* improve rendering performance, as will sharing the same path
* reference across multiple layers
A lesser known trick is sharing the same reference across multiple layers. Of course they have to use the same shape, but this is common with table/collection view cells.
I don't know why it gets faster if you share instances, i'm guessing it caches the rendering of the shadow and can reuse it for other instances in the view. I wonder if this is even faster with
With the following snippet, I'm adding a drop shadow effect to one my UIView. Which works pretty well. But as soon as I set the view's masksToBounds property to YES. The drop shadow effect isn't rendered any more.
self.myView.layer.shadowColor = [[UIColor blackColor] CGColor];
self.myView.layer.shadowOpacity = 1.0;
self.myView.layer.shadowRadius = 10.0;
self.myView.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
self.myView.layer.cornerRadius = 5.0;
self.myView.layer.masksToBounds = YES; // <-- This is causing the Drop shadow to not be rendered
UIBezierPath *path = [UIBezierPath bezierPathWithCurvedShadowForRect:self.myView.bounds];
self.myView.layer.shadowPath = path.CGPath;
self.myView.layer.shouldRasterize = YES;
Do you have any ideas on this?
Because shadow is an effect done outside the View, and that masksToBounds set to YES will tell the UIView not to draw anything that is outside itself.
If you want a roundedCorner view with shadow I suggest you do it with 2 views:
UIView *view1 = [[UIView alloc] init];
UIView *view2 = [[UIView alloc] init];
view1.layer.cornerRadius = 5.0;
view1.layer.masksToBounds = YES;
view2.layer.cornerRadius = 5.0;
view2.layer.shadowColor = [[UIColor blackColor] CGColor];
view2.layer.shadowOpacity = 1.0;
view2.layer.shadowRadius = 10.0;
view2.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
[view2 addSubview:view1];
[view1 release];
It's iOS 6 now, things might have changed. TheSquad's answer don't work for me until I managed to add one more line view2.layer.masksToBounds = NO;, otherwise shadow doesn't show. Although documentation says masksToBounds is NO by default, my code shows the opposite.
Here is how I make a rounded corner button with shadow, which is among the most commonly used code snippet in my app.
button.layer.masksToBounds = YES;
button.layer.cornerRadius = 10.0f;
view.layer.masksToBounds = NO; // critical to add this line
view.layer.cornerRadius = 10.0f;
view.layer.shadowOpacity = 1.0f;
// set shadow path to prevent horrible performance
view.layer.shadowPath =
[UIBezierPath bezierPathWithRoundedRect:_button.bounds cornerRadius:10.0f].CGPath;
[view addSubview:button];
EDIT
If views need to be animated or scrolled, masksToBounds = YES tax performance significantly, which means animation will probably get stuttered. To get rounded corner and shadow AND smooth animation or scrolling, use following code instead:
button.backgroundColor = [UIColor clearColor];
button.layer.backgroundColor = [UIColor redColor].CGColor;
button.layer.masksToBounds = NO;
button.layer.cornerRadius = 10.0f;
view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:_button.bounds cornerRadius:10.0f].CGPath;
view.layer.shadowOffset = CGSizeMake(0.0f, 4.0f);
view.layer.shadowRadius = 2.0f;
view.layer.masksToBounds = NO;
view.layer.cornerRadius = 10.0f;
[view addSubview:button];
Swift 3.0 version with StoryBoard
The same idea with #TheSquad. Create a new view under the actual view and add shadow to the lower view.
1. Create a view under the actual view
Drag a UIView to StoryBoard with same constraint as your target view. Check clip to bound for the target view. Also make sure the new view is listed before the target view so that the target view will cover the new view.
2. Now link the new view to your code add add shadow on it
This is just a sample. You can do whatever way you want here
shadowView.layer.masksToBounds = false
shadowView.layer.shadowColor = UIColor.red.cgColor
shadowView.layer.shadowOpacity = 0.5
shadowView.layer.shadowOffset = CGSize(width: -1, height: 1)
shadowView.layer.shadowRadius = 3
shadowView.layer.shadowPath = UIBezierPath(rect: coverImage.bounds).cgPath
shadowView.layer.shouldRasterize = true
This is the Swift 3 and IBDesignable version of the answer posted by #TheSquad.
I used the same concept while making changes in the storyboard file. First I moved my targetView (the one which requires corner radius and shadow) inside a new containerView. Then I added the following lines of code (Reference: https://stackoverflow.com/a/35372901/419192) to add some IBDesignable attributes for UIView Class:
#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
}
}
/* The corner radius of the view. */
#IBInspectable var cornerRadius: CGFloat {
set {
layer.cornerRadius = newValue
}
get {
return layer.cornerRadius
}
}
After adding this code, I went back to the storyboard and on selecting my containerView I could now find a new set of attributes in the attributes inspector:
Other than adding values for these attributes as per my choice, I also added a corner radius to my targetView and set the masksToBounds property as true.
I hope this helps :)
I also had drastic performance issues with shadows and rounded corners. Instead of using the shadowPath part, I used the following lines which perfectly solved the performance hit:
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = UIScreen.mainScreen.scale;
Here is one of the solutions:
#IBOutlet private weak var blockView: UIView! {
didSet {
blockView.backgroundColor = UIColor.white
blockView.layer.shadowColor = UIColor.black.cgColor
blockView.layer.shadowOpacity = 0.5
blockView.layer.shadowOffset = CGSize.zero
blockView.layer.cornerRadius = 10
}
}
#IBOutlet private weak var imageView: UIImageView! {
didSet {
imageView.layer.cornerRadius = 10
imageView.layer.masksToBounds = true
imageView.layer.shouldRasterize = true
}
}