I have a UIView called containerView.
Upon creating the UIView & Adding it to the main view, I'm trying to add a little shadow. My code:
let containerView = UIView()
let bg_clear = UIColor(hexString: "#34495E")
containerView.backgroundColor = bg_clear
containerView.layer.cornerRadius = 15
containerView.clipsToBounds = false
containerView.dropShadow() // Generate Shadow
view.addSubview(containerView)
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.heightAnchor.constraint(equalToConstant: 50).isActive = true
containerView.widthAnchor.constraint(equalToConstant: 300).isActive = true
containerView.centerXAnchor.constraint(equalTo: tabBar.centerXAnchor).isActive = true
Code to generate the Shadow:
extension UIView {
func dropShadow(scale: Bool = true) {
layer.masksToBounds = false
layer.shadowColor = UIColor(hexString: "#000000").cgColor
layer.shadowOpacity = 0.5
layer.shadowOffset = CGSize(width: -1, height: 1)
layer.shadowRadius = 1
layer.shadowPath = UIBezierPath(rect: bounds).cgPath
layer.shouldRasterize = true
layer.rasterizationScale = scale ? UIScreen.main.scale : 1
}
}
This should help :
extension UIView {
func addShadow(withOpacity opacity:Float, radius:CGFloat, andColor color:UIColor) {
self.layer.shadowColor = color.cgColor
self.layer.shadowOffset = CGSize(width:-1.0, height:1.0)
self.layer.shadowOpacity = opacity
self.layer.shadowRadius = radius
}
}
Example :
containerView.addShadow(withOpacity: 0.4, radius: 0.4, andColor: .black)
A few points to note:
Make sure your container view is not too near the left or the bottom of the screen, because the shadow is going show up to the left and below the container view.
Make sure you use a rounded rect for the shadow path, because your container view has a non-zero corner radius.
If you still can't see the shadow, try increasing the shadow offset.
You can copy and paste this code into a playground and open the live view:
extension UIView {
func dropShadow(scale: Bool = true) {
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.5
layer.shadowOffset = CGSize(width: -5, height: 5) // I made this larger
layer.shadowRadius = 1
// I used a rounded rect here
layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
layer.shouldRasterize = true
layer.rasterizationScale = scale ? UIScreen.main.scale : 1
}
}
let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view.backgroundColor = .white
let containerView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
let bg_clear = UIColor.green
containerView.backgroundColor = bg_clear
containerView.layer.cornerRadius = 15
containerView.clipsToBounds = false
containerView.dropShadow() // Generate Shadow
view.addSubview(containerView)
PlaygroundPage.current.liveView = view
You should see:
Related
With this code I get indicator but to top left corner of webView and behind borders
(I have DetailView storyboard but want to write code programmatically)
var webView = WKWebView()
var detailItem : CountriesFinal?
var spinner = UIActivityIndicatorView()
override func viewDidLoad() {
super.viewDidLoad()
spinner.frame = CGRect(x: webView.frame.width / 2, y: webView.frame.height / 2, width: 10, height: 10)
webView.addSubview(spinner)
spinner.startAnimating()
spinner.hidesWhenStopped = true
webView.backgroundColor = .blue
webView.translatesAutoresizingMaskIntoConstraints = false
webView.navigationDelegate = self
webView.frame = CGRect(x: 125, y: 150, width: 250, height: 150)
webView.layer.borderWidth = 5
webView.layer.borderColor = UIColor.red.cgColor
webView.contentMode = .scaleAspectFit
webView.backgroundColor = .blue
self.view.addSubview(webView)
I notice that this line doesn't change anything and I thought it may be the solution:
spinner.frame = CGRect(x: webView.frame.width / 2, y: webView.frame.height / 2, width: 10, height: 10)
I've tried to set translatesAutoresizingMaskIntoConstraints of spinner to false but then whole webView disappear with purple warning " Layout Issues: Position and size are ambiguous for WKWebView."
UPDATED CODE(webVIew not showing):
webView.frame = CGRect(x: 125, y: 150, width: 250, height: 150)
webView.backgroundColor = .blue
webView.translatesAutoresizingMaskIntoConstraints = false
webView.navigationDelegate = self
webView.layer.borderWidth = 5
webView.layer.borderColor = UIColor.red.cgColor
webView.contentMode = .scaleAspectFit
webView.backgroundColor = .blue
self.view.addSubview(webView)
self.view.insertSubview(spinner, aboveSubview: webView)
spinner.centerXAnchor.constraint(equalTo: webView.centerXAnchor).isActive = true
spinner.centerYAnchor.constraint(equalTo: webView.centerYAnchor).isActive = true
spinner.startAnimating()
spinner.hidesWhenStopped = true
In my opinion WKWebView is one of the system views that should probably be a "leaf" in your view hiercarchy. It is very complex in the content it manages and may not like it if you add subviews.
Instead of putting the spinner inside the WKWebView, I would probably put it into a view that is a sibling and stack it in front of the WebView in the z-order. It will float above the web view, but look like it is inside of it.
Here's a Playground I threw together that demonstrates the idea:
import UIKit
import WebKit
import PlaygroundSupport
NSSetUncaughtExceptionHandler { error in
debugPrint(error)
}
let controller = UIViewController()
let webView = WKWebView()
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView.load(URLRequest(url: URL(string: "https://www.apple.com")!))
let spinner = UIActivityIndicatorView()
spinner.translatesAutoresizingMaskIntoConstraints = false
controller.view.addSubview(webView)
webView.frame = controller.view.bounds
controller.view.insertSubview(spinner, aboveSubview: webView)
spinner.centerXAnchor.constraint(equalTo: webView.centerXAnchor).isActive = true
spinner.centerYAnchor.constraint(equalTo: webView.centerYAnchor).isActive = true
PlaygroundSupport.PlaygroundPage.current.liveView = controller
spinner.startAnimating()
Working code. Changed place of addSubview
webView.frame = CGRect(x: 125, y: 150, width: 250, height: 150)
webView.backgroundColor = .blue
webView.translatesAutoresizingMaskIntoConstraints = false
webView.layer.borderWidth = 5
webView.layer.borderColor = UIColor.red.cgColor
webView.contentMode = .scaleAspectFit
webView.backgroundColor = .blue
self.view.addSubview(webView)
self.webView.addSubview(self.spinner)//first we need to add subview then make center or something
spinner.center = CGPoint(x: webView.bounds.width / 2, y: webView.bounds.height / 2)
webView.navigationDelegate = self
spinner.startAnimating()
spinner.hidesWhenStopped = true
I want to replicate the Apple Map App Buttons as seen here:
This is the code so far:
class CustomView: UIImageView {
init(frame: CGRect, corners: CACornerMask, systemName: String) {
super.init(frame: frame)
self.createBorders(corners: corners)
self.createImage(systemName: systemName)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createBorders(corners: CACornerMask) {
self.contentMode = .scaleAspectFill
self.clipsToBounds = false
self.layer.cornerRadius = 20
self.layer.maskedCorners = corners
self.layer.masksToBounds = false
self.layer.shadowOffset = .zero
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowRadius = 20
self.layer.shadowOpacity = 0.2
self.backgroundColor = .white
let shadowAmount: CGFloat = 2
let rect = CGRect(x: 0, y: 2, width: self.bounds.width + shadowAmount * 0.1, height: self.bounds.height + shadowAmount * 0.1)
self.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
func createImage(systemName: String) {
let image = UIImage(systemName: systemName)!
let renderer = UIGraphicsImageRenderer(bounds: self.frame)
let renderedImage = renderer.image { (_) in
image.draw(in: frame.insetBy(dx: 30, dy: 30))
}
And use it like this:
let size = CGSize(width: 100, height: 100)
let frame = CGRect(origin: CGPoint(x: 100, y: 100), size: size)
let infoView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], systemName: "info.circle")
let locationView = CustomView(frame: frame, corners: [], systemName: "location")
let twoDView = CustomView(frame: frame, corners: [.layerMinXMaxYCorner, .layerMaxXMaxYCorner], systemName: "view.2d")
let binocularsView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner], systemName: "binoculars.fill")
let stackView = UIStackView(arrangedSubviews: [infoView, locationView, twoDView, binocularsView])
stackView.frame = CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 100, height: 400))
stackView.distribution = .fillEqually
stackView.axis = .vertical
stackView.setCustomSpacing(20, after: twoDView)
view.addSubview(stackView)
Which makes it look like this:
AND: There aren't the thin lines between each Icon of the stackView :/
Thanks for your help!
Not sure what's going on, because when I ran you code as-is I did not get the stretched images you've shown.
To get "thin lines between each Icon", you could either modify your custom image view to add the lines to the middle icon image, or, what might be easier, would be to use a 1-point height UIView between each icon.
Also, you'll be better off using auto-layout.
See if this gives you the desired result:
class CustomView: UIImageView {
init(frame: CGRect, corners: CACornerMask, systemName: String) {
super.init(frame: frame)
self.createBorders(corners: corners)
self.createImage(systemName: systemName)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createBorders(corners: CACornerMask) {
self.contentMode = .scaleAspectFill
self.clipsToBounds = false
self.layer.cornerRadius = 20
self.layer.maskedCorners = corners
self.layer.masksToBounds = false
self.layer.shadowOffset = .zero
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowRadius = 20
self.layer.shadowOpacity = 0.2
self.backgroundColor = .white
let shadowAmount: CGFloat = 2
let rect = CGRect(x: 0, y: 2, width: self.bounds.width + shadowAmount * 0.1, height: self.bounds.height + shadowAmount * 0.1)
self.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
func createImage(systemName: String) {
guard let image = UIImage(systemName: systemName)?.withTintColor(.blue, renderingMode: .alwaysOriginal) else { return }
let renderer = UIGraphicsImageRenderer(bounds: self.frame)
let renderedImage = renderer.image { (_) in
image.draw(in: frame.insetBy(dx: 30, dy: 30))
}
self.image = renderedImage
}
}
class TestCustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
let size = CGSize(width: 100, height: 100)
let frame = CGRect(origin: .zero, size: size)
let infoView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], systemName: "info.circle")
let locationView = CustomView(frame: frame, corners: [], systemName: "location")
let twoDView = CustomView(frame: frame, corners: [.layerMinXMaxYCorner, .layerMaxXMaxYCorner], systemName: "view.2d")
let binocularsView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner], systemName: "binoculars.fill")
// create 2 separator views
let sep1 = UIView()
sep1.backgroundColor = .blue
let sep2 = UIView()
sep2.backgroundColor = .blue
let stackView = UIStackView(arrangedSubviews: [infoView, sep1, locationView, sep2, twoDView, binocularsView])
// use .fill, not .fillEqually
stackView.distribution = .fill
stackView.axis = .vertical
stackView.setCustomSpacing(20, after: twoDView)
view.addSubview(stackView)
// let's use auto-layout
stackView.translatesAutoresizingMaskIntoConstraints = false
// respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// only need top and leading constraints for the stack view
stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 100.0),
stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 100.0),
// both separator views need height constraint of 1.0
sep1.heightAnchor.constraint(equalToConstant: 1.0),
sep2.heightAnchor.constraint(equalToConstant: 1.0),
])
// set all images to the same 1:1 ratio size
[infoView, locationView, twoDView, binocularsView].forEach { v in
v.widthAnchor.constraint(equalToConstant: size.width).isActive = true
v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
}
}
}
This is what I get with that code example:
I have a view that contains a stackView. The view is triggered on long gesture recognizer. I then use hittest to animate the contents of the stackView.
My view with the stackview is defined as follows:
lazy var starContainer : UIView = {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 182, height: 36))
view.layer.cornerRadius = 8
view.backgroundColor = .white
view.addSubview(starStack)
starStack.translatesAutoresizingMaskIntoConstraints = false
starStack.topAnchor.constraint(equalTo: view.topAnchor, constant: 1).isActive = true
starStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 1).isActive = true
starStack.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -1).isActive = true
starStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -1).isActive = true
view.layer.cornerRadius = view.frame.height / 2
// Set padding
let padding : CGFloat = 2
starStack.spacing = padding
starStack.layoutMargins = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
starStack.isLayoutMarginsRelativeArrangement = true
// Generate shadow
view.layer.shadowColor = UIColor(white: 0.4, alpha: 0.4).cgColor
view.layer.shadowRadius = 8
view.layer.shadowOpacity = 0.5
view.layer.shadowOffset = CGSize(width: 0, height: 4)
for index in 0...4 {
let imageView = UIImageView()
imageView.isUserInteractionEnabled = true
if index == 0 {
imageView.tag = 5009
}
imageView.tag = index
imageView.image = UIImage(named: "icon_starred")
imageView.tintColor = UIColor(hex: Constants.Colors.secondary)
starStack.addArrangedSubview(imageView)
arrayStar.append(imageView)
}
return view
}()
I have then added a long gesture recognizer and whenever it detects a change then I move the views within the stackview individually:
#objc func showStars(gesture : UILongPressGestureRecognizer) {
if gesture.state == .changed {
let pressedLocation = gesture.location(in: self.starContainer)
let fixedYLocation = CGPoint(x: pressedLocation.x, y: starContainer.frame.height / 2)
let hitTestView = starContainer.hitTest(fixedYLocation, with: nil)
if hitTestView is UIImageView {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
let stackView = self.starContainer.subviews.first
stackView?.subviews.forEach({ (imageView) in
imageView.transform = .identity
})
hitTestView?.transform = CGAffineTransform(translationX: 0, y: -50)
})
}
}
My question is how do I get the imageView tag that's been populated in the stackview?
Note: This animation is similar to the emoticons for a Facebook post.
You're thinking about this the wrong way. Your showStars method uses the call starContainer.hitTest() to figure out which view the user long-pressed. At that point, you know which view you are about to animate. Just remember that view.
I've wrote this custom button class
class RoundedButton: UIButton {
override func awakeFromNib() {
layer.cornerRadius = 5
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
layer.shadowOpacity = 0.2
layer.shadowRadius = 1.0
layer.masksToBounds = false
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: 5).cgPath
layer.contents = center
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale
}
}
On my iPhone X everything perfectly fits
But when I'm using a device with smaller screen, the shadow don't scale correctly according to the button size.
Is there a problem with the button class itself or is it a constraints problem? I can't find a solution.
You need to update the shadow on layoutSubviews (this should also resolve issues your code seemed to have with rotation).
class RoundedButton: UIButton {
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: 0.0)
layer.shadowRadius = 4
layer.shadowOpacity = 0.22
}
override func awakeFromNib() {
layer.cornerRadius = 5
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
layer.shadowOpacity = 0.2
layer.shadowRadius = 1.0
layer.masksToBounds = false
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: 5).cgPath
layer.contents = center
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale
}
}
Hope this helps you
So I already went through various posts on adding 2nd view for adding shadow, but I still cannot get it to work if I want to add it in UICollectionViewCell. I subclassed UICollectionViewCell, and here is my code where I add various UI elements to the cell's content view and adding shadow to the layer:
[self.contentView setBackgroundColor:[UIColor whiteColor]];
self.layer.masksToBounds = NO;
self.layer.shadowOffset = CGSizeMake(0, 1);
self.layer.shadowRadius = 1.0;
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOpacity = 0.5;
[self.layer setShadowPath:[[UIBezierPath bezierPathWithRect:self.bounds] CGPath]];
I would like to know how to add rounded corner and shadow to UICollectionViewCell.
Neither of these solutions worked for me. If you place all your subviews into the UICollectionViewCell content view, which you probably are, you can set the shadow on the cell's layer and the border on the contentView's layer to achieve both results.
cell.contentView.layer.cornerRadius = 2.0f;
cell.contentView.layer.borderWidth = 1.0f;
cell.contentView.layer.borderColor = [UIColor clearColor].CGColor;
cell.contentView.layer.masksToBounds = YES;
cell.layer.shadowColor = [UIColor blackColor].CGColor;
cell.layer.shadowOffset = CGSizeMake(0, 2.0f);
cell.layer.shadowRadius = 2.0f;
cell.layer.shadowOpacity = 0.5f;
cell.layer.masksToBounds = NO;
cell.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:cell.bounds cornerRadius:cell.contentView.layer.cornerRadius].CGPath;
Swift 3.0
self.contentView.layer.cornerRadius = 2.0
self.contentView.layer.borderWidth = 1.0
self.contentView.layer.borderColor = UIColor.clear.cgColor
self.contentView.layer.masksToBounds = true
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 2.0)
self.layer.shadowRadius = 2.0
self.layer.shadowOpacity = 0.5
self.layer.masksToBounds = false
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.contentView.layer.cornerRadius).cgPath
Swift 3 version:
cell.contentView.layer.cornerRadius = 10
cell.contentView.layer.borderWidth = 1.0
cell.contentView.layer.borderColor = UIColor.clear.cgColor
cell.contentView.layer.masksToBounds = true
cell.layer.shadowColor = UIColor.gray.cgColor
cell.layer.shadowOffset = CGSize(width: 0, height: 2.0)
cell.layer.shadowRadius = 2.0
cell.layer.shadowOpacity = 1.0
cell.layer.masksToBounds = false
cell.layer.shadowPath = UIBezierPath(roundedRect:cell.bounds, cornerRadius:cell.contentView.layer.cornerRadius).cgPath
In case it helps: Here is the swift for rounding the corners:
cell.layer.cornerRadius = 10
cell.layer.masksToBounds = true
with cell being a variable controlling the cell: often, you will use this in override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
Enjoy!
Here's my answer, close to the others, but I add a corner radius to the layer otherwise the corners won't fill in correctly. Also, this makes a nice little extension on UICollectionViewCell.
extension UICollectionViewCell {
func shadowDecorate() {
let radius: CGFloat = 10
contentView.layer.cornerRadius = radius
contentView.layer.borderWidth = 1
contentView.layer.borderColor = UIColor.clear.cgColor
contentView.layer.masksToBounds = true
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 1.0)
layer.shadowRadius = 2.0
layer.shadowOpacity = 0.5
layer.masksToBounds = false
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath
layer.cornerRadius = radius
}
}
You can call it in collectionView(_:cellForItemAt:) of the datasource once you dequeue your cell.
Here the Swift 4 solution, updated to round every corners and not only the top corners:
contentView.layer.cornerRadius = 6.0
contentView.layer.borderWidth = 1.0
contentView.layer.borderColor = UIColor.clear.cgColor
contentView.layer.masksToBounds = true
layer.shadowColor = UIColor.lightGray.cgColor
layer.shadowOffset = CGSize(width: 0, height: 2.0)
layer.shadowRadius = 6.0
layer.shadowOpacity = 1.0
layer.masksToBounds = false
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: contentView.layer.cornerRadius).cgPath
layer.backgroundColor = UIColor.clear.cgColor
Set the layer attributes for the cell, not contentView.
CALayer * layer = [cell layer];
[layer setShadowOffset:CGSizeMake(0, 2)];
[layer setShadowRadius:1.0];
[layer setShadowColor:[UIColor redColor].CGColor] ;
[layer setShadowOpacity:0.5];
[layer setShadowPath:[[UIBezierPath bezierPathWithRect:cell.bounds] CGPath]];
SWIFT 4.2
One should add this in your custom cell or cellForItemAt:
If you are using the cellForItemAt: approach substitute self -> cell
self.layer.cornerRadius = 10
self.layer.borderWidth = 1.0
self.layer.borderColor = UIColor.lightGray.cgColor
self.layer.backgroundColor = UIColor.white.cgColor
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowOffset = CGSize(width: 2.0, height: 4.0)
self.layer.shadowRadius = 2.0
self.layer.shadowOpacity = 1.0
self.layer.masksToBounds = false
This will give you a cell with a rounded border and a drop shadow.
You simply need to (a) set cornerRadius and (b) set shadowPath to be a rounded rect with the same radius as cornerRadius:
self.layer.cornerRadius = 10;
self.layer.shadowPath = [[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:self.layer.cornerRadius] CGPath];
Here's my take on the solution. It's similar to other answers, with one key difference. It doesn't create a path that's dependent on the bounds of the view. Anytime you create a path based on the bounds and provide it to the layer you can run into issues when it's resizing, and need to setup methods to update the path.
A simpler solution is to avoid using anything that is bounds dependent.
let radius: CGFloat = 10
self.contentView.layer.cornerRadius = radius
// Always mask the inside view
self.contentView.layer.masksToBounds = true
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 1.0)
self.layer.shadowRadius = 3.0
self.layer.shadowOpacity = 0.5
// Never mask the shadow as it falls outside the view
self.layer.masksToBounds = false
// Matching the contentView radius here will keep the shadow
// in sync with the contentView's rounded shape
self.layer.cornerRadius = radius
Now when ever the cells size change the view API will do all the work internally.
I had to make some slight changes for Swift:
cell.contentView.layer.cornerRadius = 2.0;
cell.contentView.layer.borderWidth = 1.0;
cell.contentView.layer.borderColor = UIColor.clearColor().CGColor;
cell.contentView.layer.masksToBounds = true;
cell.layer.shadowColor = UIColor.grayColor().CGColor;
cell.layer.shadowOffset = CGSizeMake(0, 2.0);
cell.layer.shadowRadius = 2.0;
cell.layer.shadowOpacity = 1.0;
cell.layer.masksToBounds = false;
cell.layer.shadowPath = UIBezierPath(roundedRect:cell.bounds, cornerRadius:cell.contentView.layer.cornerRadius).CGPath;
I use the following method to accomplish this kind of effect:
extension UICollectionViewCell {
/// Call this method from `prepareForReuse`, because the cell needs to be already rendered (and have a size) in order for this to work
func shadowDecorate(radius: CGFloat = 8,
shadowColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3),
shadowOffset: CGSize = CGSize(width: 0, height: 1.0),
shadowRadius: CGFloat = 3,
shadowOpacity: Float = 1) {
contentView.layer.cornerRadius = radius
contentView.layer.borderWidth = 1
contentView.layer.borderColor = UIColor.clear.cgColor
contentView.layer.masksToBounds = true
layer.shadowColor = shadowColor.cgColor
layer.shadowOffset = shadowOffset
layer.shadowRadius = shadowRadius
layer.shadowOpacity = shadowOpacity
layer.masksToBounds = false
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath
layer.cornerRadius = radius
}
}
The answer of Mike Sabatini works fine, if you configure the cell properties directly on the collectionView cellForItemAt, but if you try to set them in awakeFromNib() of a custom UICollectionViewCell subclass, you will end with a wrong bezierPath set on the devices that doesn't match the width and height previously set in your Storyboard (IB).
Solution for me was create a func inside the subclass of UICollectionViewCell and calling it from the cellForItemAt like this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath) as? CustomCollectionViewCell{
cell.configure())
return cell
}
else {
return UICollectionViewCell()
}
}
And on the CustomCollectionViewCell.swift :
class CustomCollectionViewCell: UICollectionViewCell{
func configure() {
contentView.layer.cornerRadius = 20
contentView.layer.borderWidth = 1.0
contentView.layer.borderColor = UIColor.clear.cgColor
contentView.layer.masksToBounds = true
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 2.0)
layer.shadowRadius = 2.0
layer.shadowOpacity = 0.5
layer.masksToBounds = false
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: contentView.layer.cornerRadius).cgPath}
}
This one worked for me
cell.contentView.layer.cornerRadius = 5.0
cell.contentView.layer.borderColor = UIColor.gray.withAlphaComponent(0.5).cgColor
cell.contentView.layer.borderWidth = 0.5
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor.darkGray.cgColor
border.frame = CGRect(x: 0, y: cell.contentView.frame.size.height - width, width: cell.contentView.frame.size.width, height: cell.contentView.frame.size.height)
border.borderWidth = width
cell.contentView.layer.addSublayer(border)
cell.contentView.layer.masksToBounds = true
cell.contentView.clipsToBounds = true
You can set shadow color, radius and offset in UICollectionViewDataSource method while creating a UICollectionViewCell
cell.layer.shadowColor = UIColor.gray.cgColor
cell.layer.shadowOffset = CGSize(width: 0, height: 2.0)
cell.layer.shadowRadius = 1.0
cell.layer.shadowOpacity = 0.5
cell.layer.masksToBounds = false
I found an important thing: the UICollectionViewCell must have the backgroundColor as clear color in order to make these above work.
Swift 5, Xcode 13, iOS 14
First config your collection as below:
self.collectionView.clipsToBounds = false
Then config your cell same as below:
override func awakeFromNib() {
super.awakeFromNib()
self.configView()
}
private func configView() {
self.clipsToBounds = false
self.backgroundColor = .systemBackground
self.layer.cornerRadius = 10
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 0.0)
self.layer.shadowRadius = 10
self.layer.shadowOpacity = 0.2
}
Note to those two "clipToBounds = false" commands.
Just that.