I want to see my UIView with added shadow like the example image below in storyboard. Unfortunately, there isn't an existing option to set it. Is there a way to set up the shadow using keypath so I can view the shadow in storyboard?
EDIT:
I have tried to render a shadow with the following code:
import Foundation
import UIKit
#IBDesignable
class ShadowedView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
let shadowPath = UIBezierPath(rect: self.bounds)
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.blackColor().CGColor
self.layer.shadowOffset = CGSize(width: 0, height: 0.5)
self.layer.shadowOpacity = 0.2
self.layer.shadowPath = shadowPath.CGPath
self.clipsToBounds = false
}
}
#IBDesignable allowed me to see the changes immediately, but the shadow still is not present in the storyboard. Here is a screenshot of it:
And the result should be the example image. Thank you!
Image credit to #Wezly
You would need to override drawRect: or prepareForInterfaceBuilder depending on your needs. You can read more in the docs : https://developer.apple.com/library/ios/recipes/xcode_help-IB_objects_media/Chapters/CreatingaLiveViewofaCustomObject.html
Bottom line : you will have to add the code you posted in the drawRect: method in your custom view annotated as IBDesignable and Interface Builder will render it for you.
Related
Background: My app allows users to select a gradient border to apply to UITableViewCells that are dynamically sized based on the content within them. I am currently creating this border by inserting a CAGradientLayer sublayer to a UIView that sits within the cell.
Issue: Because each cell is sized differently, I am resizing the CAGradientLayer by overriding layoutIfNeeded in my custom cell class. This works, but seems suboptimal because the border is being redrawn over and over again and flickers as the cell is resizing.
Link to Screen Capture:
https://drive.google.com/file/d/1SiuNozyUM7LCdYImZoGCWeoeBKu2Ulcw/view?usp=sharing
Question: Do I need to take a different approach to creating this border? Or am I missing something regarding the UITableViewCell lifecycle? I have come across similar issues on SO, but none that seem to address this redraw issue. Thank you for your help.
CAGradientLayer Extension to Create Border
extension CAGradientLayer {
func createBorder(view: UIView, colors: [CGColor]) {
self.frame = CGRect(origin: CGPoint.zero, size: view.bounds.size)
self.colors = colors
let shape = CAShapeLayer()
shape.lineWidth = 14
shape.path = UIBezierPath(roundedRect: view.bounds, cornerRadius: 12).cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
self.mask = shape
}
}
TableViewCell Class - Insert CAGradientLayer
override func awakeFromNib() {
super.awakeFromNib()
reportCard.layer.insertSublayer(gradientLayer, at: 0)
...
}
TableViewCell Class - Resize the Border and Apply User Selected Design
override func layoutIfNeeded() {
super.layoutIfNeeded()
switch currentReport?.frameId {
case "sj_0099_nc_frame_001":
gradientLayer.createBorder(view: reportCard, colors: [App.BorderColors.lavender, App.BorderColors.white])
case "sj_0099_nc_frame_002":
gradientLayer.createBorder(view: reportCard, colors: [App.BorderColors.red, App.BorderColors.white])
case "sj_0099_nc_frame_003":
gradientLayer.createBorder(view: reportCard, colors: [App.BorderColors.yellow, App.BorderColors.white])
default:
gradientLayer.createBorder(view: reportCard, colors: [App.BorderColors.white, App.BorderColors.white])
}
}
Turns out I was looking in the wrong place all along. The code in my original post is functional, and updating the gradientLayer frame in layoutIfNeeded() or setNeedsLayout() rather than layoutSubviews() accurately draws the gradientLayer. Per Apple documentation, layoutSubviews() should not be called directly.
The source of the bug was not in my custom cell, but in my tableViewController. I had an extraneous call to reloadData().
Instead of inside awakeFromNib() use this
override func layoutSubviews() {
super.layoutSubviews()
reportCard.layer.insertSublayer(gradientLayer, at: 0)
reportCard.clipsToBounds = true
}
I am trying to create custom table view cell which works fine in my other UIViewControllers. However, in one of my controllers, the shadow is not growing, I can barely see the shadow.
Here is an image of the shadow being shown in red, you can see it is barely visible.
My cell has a UIView added inside the contentView to creating floating cell effects - the same code and same storyboard layouts are being used across my controllers but this is the only table view where the shadow issue is occurring - so I must be missing something.
My addShadow extension:
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
}
}
My awakeFromNib on the custom cell:
:: cellContentView is my UIView added to the base contentView of the cell.
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = .clear
self.selectionStyle = .none
cellContentView?.layer.masksToBounds = true
cellContentView?.round(corners: [.topLeft, .topRight, .bottomLeft, .bottomRight], radius: 10)
cellContentView?.addShadow(offset: CGSize(width: 40, height: 60), color: UIColor.red, radius: 10, opacity: 1)
cellContentView?.layer.shouldRasterize = true
}
Note: The .round is an extension being used on all my cells.
No matter what radius or offset I add for this shadow, it does not get bigger than the image. Also, none of my other cells in the their controllers require the shouldRasterize property to be set, but this does.
Does anyone know what is happening here?
Thanks :)
Edit
Strangely, if I add constraints around my view to keep the gaps large between my view and the cell content view, the background colour disappears - this is set to white in the storyboard.
You should call in the layoutSubviews method. because shadow should add after the view is uploaded
override func awakeFromNib() {
super.awakeFromNib()
//init methods
}
override public func layoutSubviews() {
super.layoutSubviews()
//Added shadow
self.reloadLayers()
}
private func reloadLayers() {
self.layer.cornerRadius = 5
self.addShadow(.TransactionCell)
}
I hope it helps
Content view will fill you cell, so you need to add shadow to view inside content view which has all your components inside it. Then add constraints to it with gap between that view and content view. Second, 40 and 60 properties for shadow is likely too large, when I said too large I mean unbelievable large, because gap between content views in cells are no more than 15 - 30 even less. so try it with much less values, while radius can remain 10 but you will see what value fit the best. If cell content view is your custom view just values will did the job if your view is not kind of transparent or any inside it, in that case it won't, and there is hard to fix that, I tried many libraries and custom codes and it is never ok.
squircleView.layer.cornerRadius = 40
squircleView.layer.cornerCurve = CALayerCornerCurve.continuous
squircleView.layer.shadowColor = UIColor.systemGray.cgColor
squircleView.layer.shadowOpacity = 0.7
squircleView.layer.shadowOffset = CGSize(width: 0, height: 0.5)
squircleView.layer.shadowRadius = 5
The class given below is working fine
#IBDesignable class iButton : UIButton {
#IBInspectable var cornerRadius : CGFloat = 0.0{
didSet{
layer.cornerRadius = cornerRadius
}
}}
But question is, when i set cornerRadius value 35 in Attribute Inspector for the button with size of (width : 70, Height 70). Im getting rounded button on the storyboard but while running it on Simulator its not circular instead its rounded rectangle.
My Design View on xCode is iPhone-SE and simulated on iPhone-7-plus simulator.
I have also enabled autoresize by setting the height and width on the Size Inspector.
Out of my knowledge the corner radius should be the half of the width. And when the button is resized by auto-resizing why not the corner radius is resized. how can i fix this?
Thanks in advance.
import UIKit
#IBDesignable
class RoundedCornerButton: UIButton {
override func drawRect(rect: CGRect) {
self.clipsToBounds = true
}
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
self.layer.cornerRadius = cornerRadius
}
}
}
or you can check this link this link for more info
https://www.youtube.com/watch?v=JQ5i2YKwvJ8
Thank You i got answer.
We also need to scale the cornerRadius.
layer.cornerRadius = cornerRadius * (UIScreen.main.bounds.width/320.0) //320.0 be my width of the screen that i designed on storyboard
I have a subclass of UIButton:
class ColorButton: UIButton {
override func awakeFromNib() {
self.layer.backgroundColor = UIColor.blackColor().CGColor
self.layer.cornerRadius = frame.size.width / 2
self.clipsToBounds = true
}
}
In interface builder, I set the button with 4 constraints: width = 100, height = 100, centerX, centerY.
The button disappears when I run my code on the simulator. However, if it set
self.layer.cornerRadius = 50
it works. I cannot figure it out. If anybody understand this problem, please tell me.
Add in awakeFromNib first line:
self.layoutIfNeeded()
Code:
class ColorButton: UIButton {
override func awakeFromNib() {
self.layoutIfNeeded()
self.layer.backgroundColor = UIColor.blackColor().CGColor
self.layer.cornerRadius = frame.size.width / 2
self.clipsToBounds = true
}
}
Your code works just fine in a fresh project so I suspect the problem is somewhere else. You forgot to call super.awakeFromNib() though. From Apple docs:
You must call the super implementation of awakeFromNib to give parent
classes the opportunity to perform any additional initialization they
require. Although the default implementation of this method does
nothing, many UIKit classes provide non-empty implementations. You may
call the super implementation at any point during your own
awakeFromNib method.
Here I've got screen designed in Sketch:
There are two buttons. Now I am designing such button in Xcode and I am setting UIButton's corner radius this way:
layer.cornerRadius = 30
clipsToBounds = true
But as the result I am having something strange:
You can see some corner in the center if the left and right sides of button. What can I do?
To generate round corners on such a button, if you set the cornerRadius value to 30, you are assuming that the height of the button is set to 60.
This may not be true on all the devices, depending on how you handle your layout. Seeing your image, it looks like the button is slightly less high than what you designed.
Two options :
Use Auto-Layout and add a constraint of "fixed height" on your button, with a value of 60, so your button always has a height of 60 points.
Implement a UIButton subclass, and in the layoutSubviews method, set the cornerRadius to half the height of the bounds of your button. This way, any time the system re-draws the button, the corner radius will be updated appropriately.
Make a custom UIButton Class and set all of your desired buttons to that class so you don't need to put any more code in every UIViewController class.
The custom UIButton class should look like this:
import UIKit
class RoundCornerButton: UIButton {
override func drawRect(rect: CGRect) {
// Drawing code
self.clipsToBounds = true
self.layer.cornerRadius = self.frame.size.width / 2
}
}
Your button's layer has a different size now, than in Sketch.
After layoutSubviews set the corner radius to be the half of the layers height.
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = layer.bounds.height / 2.0
}
Instead of writing code in every controller if you are going to use multiple times, You can make extension for making circular buttons, views as well using below code.In which you can pass cornerRadius, borderWidth and UIColor.
import UIKit
extension UIView
{
func makeCircular(cornerRadius: CGFloat, borderWidth: CGFloat, borderColor: UIColor)
{
self.layer.cornerRadius = cornerRadius * self.bounds.size.width
self.layer.borderColor = borderColor.CGColor as CGColorRef
self.layer.borderWidth = borderWidth
self.clipsToBounds = true
}
}
you can simply pass the values as per your requirement.
Usage :
snapButton.makeCircular(0.5, borderWidth: 1.0, borderColor: UIColor.clearColor())