Swift: UIButton Customization - ios

I would like to move the code to customize UIButtons out of my viewcontroller classes as a best practice. The code I have below is to add a white border to UIButtons and I would like to easily call it on buttons throughout my project.
//White Border
let passwordBorder = CALayer()
let width = CGFloat(5.0)
passwordBorder.borderColor = UIColor.whiteColor().CGColor
passwordBorder.frame = CGRect(x: 0, y: 0, width: passwordField.frame.size.width, height: passwordField.frame.size.height)
passwordBorder.borderWidth = width
passwordField.layer.addSublayer(passwordBorder)
passwordField.layer.masksToBounds = true
How would I put this code into a helper function so I could call it easily?
I am new to coding and am having trouble with helper functions on anything UI. Thanks!

Take a look at Swift's Extensions. You could pretty easily do something like
extension UIButton {
func setPasswordBorderColor(borderColor: UIColor) {
//White Border
let passwordBorder = CALayer()
let width = CGFloat(5.0)
passwordBorder.borderColor = borderColor.CGColor
passwordBorder.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)
passwordBorder.borderWidth = width
self.layer.addSublayer(passwordBorder)
self.layer.masksToBounds = true
}
}

import UIKit
//
#IBDesignable
class CustomBorderButton: UIButton {
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor: UIColor? {
didSet {
layer.borderColor = borderColor?.CGColor
}
}
}

Without Code you can configure your button
Select Your button then Select Identity Inspector then add
"User Defined Runtime Attribute".See screen Shot for more details
Add CALayer+XibConfiguration.h & CALayer+XibConfiguration.m in your Project
for CALayer+XibConfiguration open this link & download CALayer+XibConfiguration

There are several ways by which you can achieve this like using category, subclassing UIButton, or create a function in a class (may be base class inherited by all other classes.), etc.
By using function you ca do
func cutomizeButton(frame : CGRect, title : String) -> UIButton {
let button : UIButton = UIButton(type: .Custom)
button.frame = frame
button.layer.borderWidth = 5.0
button.layer.borderColor = UIColor.whiteColor().CGColor;
button.layer.masksToBounds = true
button.titleLabel?.text = title;
//do other stuff
return button;
}

In helper class make method like
func customiseButton(button:UIButton){
//White Border
let passwordBorder = CALayer()
let width = CGFloat(5.0)
passwordBorder.borderColor = UIColor.whiteColor().CGColor
passwordBorder.frame = CGRect(x: 0, y: 0, width: button.frame.size.width, height: button.frame.size.height)
passwordBorder.borderWidth = width
button.layer.addSublayer(passwordBorder)
button.layer.masksToBounds = true
}
and in ViewController call this method as
HelperClass().customiseButton(passwordField)

You can try with this Extension for round button:
extension UIButton{
func roundCorners(corners:UIRectCorner, radius: CGFloat){
let borderLayer = CAShapeLayer()
borderLayer.frame = self.layer.bounds
borderLayer.strokeColor = UIColor.green.cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.lineWidth = 10.5
let path = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius))
borderLayer.path = path.cgPath
self.layer.addSublayer(borderLayer)
}
}

Related

Swift textfields without border

I am new to swift. Your help will be really appreciated.
I have two textfields in my application. How would I create same UI as given in the pic below.
I want to create textfields with only one below border as given in the screenshot.
https://www.dropbox.com/s/wlizis5zybsvnfz/File%202017-04-04%2C%201%2052%2024%20PM.jpeg?dl=0
#IBOutlet var textField: UITextField! {
didSet {
let border = CALayer()
let width: CGFloat = 1 // this manipulates the border's width
border.borderColor = UIColor.darkGray.cgColor
border.frame = CGRect(x: 0, y: textField.frame.size.height - width,
width: textField.frame.size.width, height: textField.frame.size.height)
border.borderWidth = width
textField.layer.addSublayer(border)
textField.layer.masksToBounds = true
}
}
Create a subclass of UITextField so you can reuse this component across multiple views without have to re implement the drawing code. Expose various properties via #IBDesignable and #IBInspectable and you can have control over color and thickness in the story board. Also - implement a "redraw" on by overriding layoutSubviews so the border will adjust if you are using auto layout and there is an orientation or perhaps constraint based animation. That all said - effectively your subclass could look like this:
import UIKit
class Field: UITextField {
private let border = CAShapeLayer()
#IBInspectable var color: UIColor = UIColor.blue {
didSet {
border.strokeColor = color.cgColor
}
}
#IBInspectable var thickness: CGFloat = 1.0 {
didSet {
border.lineWidth = thickness
}
}
override func draw(_ rect: CGRect) {
self.borderStyle = .none
let from = CGPoint(x: 0, y: rect.height)
let here = CGPoint(x: rect.width, y: rect.height)
let path = borderPath(start: from, end: here).cgPath
border.path = path
border.strokeColor = color.cgColor
border.lineWidth = thickness
border.fillColor = nil
layer.addSublayer(border)
}
override func layoutSubviews() {
super.layoutSubviews()
let from = CGPoint(x: 0, y: bounds.height)
let here = CGPoint(x: bounds.width, y: bounds.height)
border.path = borderPath(start: from, end: here).cgPath
}
private func borderPath(start: CGPoint, end: CGPoint) -> UIBezierPath {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
return path
}
}
Then when you add a text field view to your story board - update the class in the Identity Inspector to use this subclass, Field - and then in the attributes inspector, you can set color and thickness.
Add border at Bottom in UITextField call below function:
func setTextFieldBorder(_ dimension: CGRect) -> CALayer {
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor.darkGray.cgColor
border.frame = CGRect(x: 0, y: dimension.size.height - width, width: dimension.size.width, height: dimension.size.height)
border.borderWidth = width
return border
}
How to set UITextField border in textField below sample code for that:
txtDemo.layer.addSublayer(setTextFieldBorder(txtDemo.frame))
txtDemo.layer.masksToBounds = true
Where txtDemo is IBOutlet of UITextField.

UITextField shadow won't show

In my xib file, I have created a generic function through which I wanted to add a bottom border through implementing a shadow to those textfields
func setupTextField(textField: UITextField) {
textField.leftViewMode = UITextFieldViewMode.always
let imageView = UIImageView();
let image = UIImage(named: "calendar");
imageView.image = image;
imageView.frame = CGRect(x: 5, y: 8, width: 20, height: 20)
textField.addSubview(imageView)
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: textField.frame.height))
textField.leftView = paddingView
textField.borderStyle = .none
textField.layer.backgroundColor = UIColor.white.cgColor
textField.layer.masksToBounds = false
textField.layer.shadowColor = UIColor(hex: "#D8D8D8").cgColor
textField.layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
textField.layer.shadowOpacity = 1.0
textField.layer.shadowRadius = 0.0
}
And I have called this function from my awakeFromNib() method and as a result it added the shadow on my 2nd UITextField but not at the 1st one. Though from debug I can see that if there is no 2nd UITextField then this would draw a shadow on my 1st one.
How to overcome this problem?
For swift 4, swift 4.2, swift 5 Please refer below answer
Can give shadow to any UIView sub classes(imageview, label, textfield, textview etc)
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.4 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 (1, 2). 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
}
}
}
Textfield shadow
swift 4.2
textFieldDOB.backgroundColor = UIColor.white textFieldDOB.clipsToBounds = false
textFieldDOB.layer.shadowColor = UIColor.black.cgColor
textFieldDOB.layer.shadowOffset = CGSize.zero
textFieldDOB.layer.shadowOpacity = 0.3
textFieldDOB.layer.shadowRadius = 6
textFieldDOB.borderStyle = .none

How to only show bottom border of UITextField in Swift

I want to show only bottom border and hide the other sides.
Output I see: As you can see I see the top, left and right borders also and they are black in color, I want to remove them. Only need the bottom white thick 2.0 border.
Code I am using (source):
var border = CALayer()
var width = CGFloat(2.0)
border.borderColor = UIColor.whiteColor().CGColor
border.frame = CGRect(x: 0, y: tv_username.frame.size.height - width, width: tv_username.frame.size.width, height: tv_username.frame.size.height)
border.borderWidth = width
tv_username.backgroundColor = UIColor.clearColor()
tv_username.layer.addSublayer(border)
tv_username.layer.masksToBounds = true
tv_username.textColor = UIColor.whiteColor()
Try to do by this way, with Swift 5.1:
var bottomLine = CALayer()
bottomLine.frame = CGRect(x: 0.0, y: myTextField.frame.height - 1, width: myTextField.frame.width, height: 1.0)
bottomLine.backgroundColor = UIColor.white.cgColor
myTextField.borderStyle = UITextField.BorderStyle.none
myTextField.layer.addSublayer(bottomLine)
You have to set the borderStyle property to None
If you are using the autolayout then set perfect constraint else bottomline will not appear.
Hope it helps.
Thought from #Ashish's answer, used same approach long ago in Objective-C but implementing extension will be more useful.
extension UITextField {
func addBottomBorder(){
let bottomLine = CALayer()
bottomLine.frame = CGRect(x: 0, y: self.frame.size.height - 1, width: self.frame.size.width, height: 1)
bottomLine.backgroundColor = UIColor.white.cgColor
borderStyle = .none
layer.addSublayer(bottomLine)
}
}
In your controller:
self.textField.addBottomBorder()
Can add further parameters to your method, like adding border height, color.
#mina-fawzy
I liked the answer that included masksToBounds by Mina Fawzy...
I ran into this issue where I was trying to style a bottom border of a UITextField, and the comments using a CGRect worked for me, however, I ran into issues when using different screen sizes, or if I changed the orientation to landscape view from the portrait.
ie. My Xcode Main.storyboard was designed with iPhone XS Max, with a UITextField constrained to be 20 points from the left/right of the screen. In my viewDidLoad() I stylized the UITextField (textfield) using the CGRect approach, making the width of the rectangle equal to textfield.frame.width.
When testing on the iPhone XS Max, everything worked perfectly, BUT, when I tested on iPhone 7 (smaller screen width) the CGRect was grabbing the width of the iPhone XS Max during the viewDidLoad(), causing the rectangle (bottom line) to be too wide, and the right edge went off the screen. Similarly, when I tested on iPad screens, the bottom line was way too short. And also, on any device, rotating to landscape view did not re-calculate the size of the rectangle needed for the bottom line.
The best solution I found was to set the width of the CGRect to larger than the longest iPad dimension (I randomly chose 2000) and THEN added textfield.layer.masksToBounds = true. This worked perfectly because now the line is plenty long from the beginning, does not need to be re-calculated ever, and is clipped to the correct width of the UITextField no matter what screen size or orientation.
Thanks Mina, and hope this helps others with the same issue!
Objective C
[txt.layer setBackgroundColor: [[UIColor whiteColor] CGColor]];
[txt.layer setBorderColor: [[UIColor grayColor] CGColor]];
[txt.layer setBorderWidth: 0.0];
[txt.layer setCornerRadius:12.0f];
[txt.layer setMasksToBounds:NO];
[txt.layer setShadowRadius:2.0f];
txt.layer.shadowColor = [[UIColor blackColor] CGColor];
txt.layer.shadowOffset = CGSizeMake(1.0f, 1.0f);
txt.layer.shadowOpacity = 1.0f;
txt.layer.shadowRadius = 1.0f;
Swift
textField.layer.backgroundColor = UIColor.whiteColor().CGColor
textField.layer.borderColor = UIColor.grayColor().CGColor
textField.layer.borderWidth = 0.0
textField.layer.cornerRadius = 5
textField.layer.masksToBounds = false
textField.layer.shadowRadius = 2.0
textField.layer.shadowColor = UIColor.blackColor().CGColor
textField.layer.shadowOffset = CGSizeMake(1.0, 1.0)
textField.layer.shadowOpacity = 1.0
textField.layer.shadowRadius = 1.0
I have tried all this answer but no one worked for me except this one
let borderWidth:CGFloat = 2.0 // what ever border width do you prefer
let bottomLine = CALayer()
bottomLine.frame = CGRectMake(0.0, Et_textfield.height - borderWidth, Et_textfield.width, Et_textfield.height )
bottomLine.backgroundColor = UIColor.blueColor().CGColor
bottomLine
Et_textfield.layer.addSublayer(bottomLine)
Et_textfield.layer.masksToBounds = true // the most important line of code
Swift 3:
Just subclass your UITextField
class BottomBorderTF: UITextField {
var bottomBorder = UIView()
override func awakeFromNib() {
//MARK: Setup Bottom-Border
self.translatesAutoresizingMaskIntoConstraints = false
bottomBorder = UIView.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
bottomBorder.backgroundColor = UIColor.orange
bottomBorder.translatesAutoresizingMaskIntoConstraints = false
addSubview(bottomBorder)
//Mark: Setup Anchors
bottomBorder.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
bottomBorder.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
bottomBorder.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
bottomBorder.heightAnchor.constraint(equalToConstant: 1).isActive = true // Set Border-Strength
}
}
Solution which using CALayer is not good because when device is rotated the underline doesn't change width.
class UnderlinedTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
let bottomLine = CALayer()
bottomLine.frame = CGRect(x: 0, y: self.frame.size.height, width: UIScreen.main.bounds.width, height: 1)
bottomLine.bounds = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: self.frame.size.height)
bottomLine.backgroundColor = UIColor.black.cgColor
borderStyle = .none
layer.addSublayer(bottomLine)
layer.masksToBounds = true
}
}
The best solution is to use UIView.
class UnderlinedTextField: UITextField {
private let defaultUnderlineColor = UIColor.black
private let bottomLine = UIView()
override func awakeFromNib() {
super.awakeFromNib()
borderStyle = .none
bottomLine.translatesAutoresizingMaskIntoConstraints = false
bottomLine.backgroundColor = defaultUnderlineColor
self.addSubview(bottomLine)
bottomLine.topAnchor.constraint(equalTo: self.bottomAnchor, constant: 1).isActive = true
bottomLine.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
bottomLine.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
bottomLine.heightAnchor.constraint(equalToConstant: 1).isActive = true
}
public func setUnderlineColor(color: UIColor = .red) {
bottomLine.backgroundColor = color
}
public func setDefaultUnderlineColor() {
bottomLine.backgroundColor = defaultUnderlineColor
}
}
First set borderStyle property to .none.
Also, don't forget that the best time to call this method in the viewDidAppear(_:) method.
To make it handy, you can use an extension:
extension UIView {
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - width,
width: self.frame.size.width, height: width)
self.layer.addSublayer(border)
}
}
Call it like:
textfield.addBottomBorderWithColor(color: UIColor.lightGray, width: 0.5)
Using extension and Swift 5.3
extension UITextField {
internal func addBottomBorder(height: CGFloat = 1.0, color: UIColor = .black) {
let borderView = UIView()
borderView.backgroundColor = color
borderView.translatesAutoresizingMaskIntoConstraints = false
addSubview(borderView)
NSLayoutConstraint.activate(
[
borderView.leadingAnchor.constraint(equalTo: leadingAnchor),
borderView.trailingAnchor.constraint(equalTo: trailingAnchor),
borderView.bottomAnchor.constraint(equalTo: bottomAnchor),
borderView.heightAnchor.constraint(equalToConstant: height)
]
)
}
}
For those looking for a solution that works for Autolayout, IBInspectable, and the Storyboard, subclass UITextField into your custom textfield class and add these:
func setUnderline() {
for sub in self.subviews {
sub.removeFromSuperview()
}
if underlineStyle == true {
var bottomBorder = UIView()
bottomBorder = UIView.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
bottomBorder.backgroundColor = borderColor //YOUR UNDERLINE COLOR HERE
bottomBorder.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(bottomBorder)
bottomBorder.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
bottomBorder.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
bottomBorder.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
bottomBorder.heightAnchor.constraint(equalToConstant: underlineHeight).isActive = true
layoutIfNeeded()
}
}
#IBInspectable var underlineStyle: Bool = false {
didSet {
setUnderline()
}
}
#IBInspectable var underlineHeight: CGFloat = 0 {
didSet {
setUnderline()
}
}
Swift 5.
extension UITextField {
let bottomLine = UIView()
bottomLine.backgroundColor = .black
borderStyle = .none
self.addSubview(bottomLine)
NSLayoutConstraint.activate([
bottomLine.topAnchor.constraint(equalTo: bottomAnchor + 10),
bottomLine.leadingAnchor.constraint(equalTo: leadingAnchor),
bottomLine.trailingAnchor.constraint(equalTo: trailingAnchor),
bottomLine.heightAnchor.constraint(equalToConstant: 1)
])
}
For multiple Text Field
override func viewDidLoad() {
configureTextField(x: 0, y: locationField.frame.size.height-1.0, width: locationField.frame.size.width, height:1.0, textField: locationField)
configureTextField(x: 0, y: destinationField.frame.size.height-1.0, width: destinationField.frame.size.width, height:1.0, textField: destinationField)
configureTextField(x: 0, y: originField.frame.size.height-1.0, width: originField.frame.size.width, height:1.0, textField: originField)
configureTextField(x: 0, y: nameField.frame.size.height-1.0, width: nameField.frame.size.width, height:1.0, textField: nameField)
locationField.text="Hello"
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func configureTextField(x:CGFloat,y:CGFloat,width:CGFloat,height:CGFloat,textField:UITextField)
{
let bottomLine = CALayer()
bottomLine.frame = CGRect(x: x, y: y, width: width, height: height)
bottomLine.backgroundColor = UIColor.white.cgColor
textField.borderStyle = UITextBorderStyle.none
textField.layer.addSublayer(bottomLine)
}
Textfield bottom border set but some more issues for devices.So bottom border not fit in textfield.I retrieve that problem the code like this
It works fine
swift 4.2
let bottomLine = CALayer()
bottomLine.frame = CGRect(x: 0.0, y: textField.frame.height - 1, width: screenSize.width - 32, height: 1.0)
bottomLine.backgroundColor = UIColor(hex: 0xD5D5D5).cgColor
textField.borderStyle = UITextField.BorderStyle.none
textField.layer.addSublayer(bottomLine)
For swift 4. this works for me.
let myfield:UITextField = {
let mf=UITextField()
let atributePlaceHolder=NSAttributedString(string: "Text_description", attributes:[NSAttributedString.Key.foregroundColor :UIColor.darkGray])
mf.textColor = .gray
mf.attributedPlaceholder=atributePlaceHolder
mf.layer.borderColor = UIColor.black.cgColor
mf.layer.backgroundColor = UIColor.white.cgColor
mf.layer.borderWidth = 0.0
mf.layer.shadowOffset = CGSize(width: 0, height: 1.0)
mf.layer.shadowOpacity = 1.0
mf.layer.shadowRadius = 0.0
return mf
}()

Swift - Problems with corner radius and drop shadow

I'm trying to create a button with rounded corners and a drop shadow. No matter how I switch up, the button will not display correctly. I've tried masksToBounds = false and masksToBounds = true, but either the corner radius works and the shadow does not or the shadow works and the corner radius doesn't clip the corners of the button.
import UIKit
import QuartzCore
#IBDesignable
class Button : UIButton
{
#IBInspectable var masksToBounds: Bool = false {didSet{updateLayerProperties()}}
#IBInspectable var cornerRadius : CGFloat = 0 {didSet{updateLayerProperties()}}
#IBInspectable var borderWidth : CGFloat = 0 {didSet{updateLayerProperties()}}
#IBInspectable var borderColor : UIColor = UIColor.clearColor() {didSet{updateLayerProperties()}}
#IBInspectable var shadowColor : UIColor = UIColor.clearColor() {didSet{updateLayerProperties()}}
#IBInspectable var shadowOpacity: CGFloat = 0 {didSet{updateLayerProperties()}}
#IBInspectable var shadowRadius : CGFloat = 0 {didSet{updateLayerProperties()}}
#IBInspectable var shadowOffset : CGSize = CGSizeMake(0, 0) {didSet{updateLayerProperties()}}
override func drawRect(rect: CGRect)
{
updateLayerProperties()
}
func updateLayerProperties()
{
self.layer.masksToBounds = masksToBounds
self.layer.cornerRadius = cornerRadius
self.layer.borderWidth = borderWidth
self.layer.borderColor = borderColor.CGColor
self.layer.shadowColor = shadowColor.CGColor
self.layer.shadowOpacity = CFloat(shadowOpacity)
self.layer.shadowRadius = shadowRadius
self.layer.shadowOffset = shadowOffset
}
}
The following Swift 5 / iOS 12 code shows how to set a subclass of UIButton that allows to create instances with rounded corners and shadow around it:
import UIKit
final class CustomButton: UIButton {
private var shadowLayer: CAShapeLayer!
override func layoutSubviews() {
super.layoutSubviews()
if shadowLayer == nil {
shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
shadowLayer.shadowOpacity = 0.8
shadowLayer.shadowRadius = 2
layer.insertSublayer(shadowLayer, at: 0)
//layer.insertSublayer(shadowLayer, below: nil) // also works
}
}
}
According to your needs, you may add a UIButton in your Storyboard and set its class to CustomButton or you may create an instance of CustomButton programmatically. The following UIViewController implementation shows how to create and use a CustomButton instance programmatically:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = CustomButton(type: .system)
button.setTitle("Button", for: .normal)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint = button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let verticalConstraint = button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let widthConstraint = button.widthAnchor.constraint(equalToConstant: 100)
let heightConstraint = button.heightAnchor.constraint(equalToConstant: 100)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
}
The previous code produces the image below in the iPhone simulator:
My custom button with some shadow and rounded corners, I use it directly within the Storyboard with no need to touch it programmatically.
Swift 4
class RoundedButtonWithShadow: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
self.layer.masksToBounds = false
self.layer.cornerRadius = self.frame.height/2
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
self.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
self.layer.shadowOpacity = 0.5
self.layer.shadowRadius = 1.0
}
}
To expand on Imanou's post, it's possible to programmatically add the shadow layer in the custom button class
#IBDesignable class CustomButton: UIButton {
var shadowAdded: Bool = false
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
if shadowAdded { return }
shadowAdded = true
let shadowLayer = UIView(frame: self.frame)
shadowLayer.backgroundColor = UIColor.clearColor()
shadowLayer.layer.shadowColor = UIColor.darkGrayColor().CGColor
shadowLayer.layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: self.cornerRadius).CGPath
shadowLayer.layer.shadowOffset = CGSize(width: 1.0, height: 1.0)
shadowLayer.layer.shadowOpacity = 0.5
shadowLayer.layer.shadowRadius = 1
shadowLayer.layer.masksToBounds = true
shadowLayer.clipsToBounds = false
self.superview?.addSubview(shadowLayer)
self.superview?.bringSubviewToFront(self)
}
}
An alternative way to get more usable and consistent button.
Swift 2:
func getImageWithColor(color: UIColor, size: CGSize, cornerRadius:CGFloat) -> UIImage {
let rect = CGRectMake(0, 0, size.width, size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 1)
UIBezierPath(
roundedRect: rect,
cornerRadius: cornerRadius
).addClip()
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
let button = UIButton(type: .Custom)
button.frame = CGRectMake(20, 20, 200, 50)
button.setTitle("My Button", forState: UIControlState.Normal)
button.setTitleColor(UIColor.blackColor(), forState: UIControlState.Normal)
self.addSubview(button)
let image = getImageWithColor(UIColor.whiteColor(), size: button.frame.size, cornerRadius: 5)
button.setBackgroundImage(image, forState: UIControlState.Normal)
button.layer.shadowRadius = 5
button.layer.shadowColor = UIColor.blackColor().CGColor
button.layer.shadowOpacity = 0.5
button.layer.shadowOffset = CGSizeMake(0, 1)
button.layer.masksToBounds = false
Swift 3:
func getImageWithColor(_ color: UIColor, size: CGSize, cornerRadius:CGFloat) -> UIImage? {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
let button = UIButton(type: .custom)
button.frame = CGRect(x:20, y:20, width:200, height:50)
button.setTitle("My Button", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
self.addSubview(button)
if let image = getImageWithColor(UIColor.white, size: button.frame.size, cornerRadius: 5) {
button.setBackgroundImage(image, for: .normal)
}
button.layer.shadowRadius = 5
button.layer.shadowColor = UIColor.black.cgColor
button.layer.shadowOpacity = 0.5
button.layer.shadowOffset = CGSize(width:0, height:1)
button.layer.masksToBounds = false
Swift 5 &
No need of "UIBezierPath"
view.layer.cornerRadius = 15
view.clipsToBounds = true
view.layer.masksToBounds = false
view.layer.shadowRadius = 7
view.layer.shadowOpacity = 0.6
view.layer.shadowOffset = CGSize(width: 0, height: 5)
view.layer.shadowColor = UIColor.red.cgColor
Refactored this to support any view. Subclass your view from this and it should have rounded corners. If you add something like a UIVisualEffectView as a subview to this view you likely need to use the same rounded corners on that UIVisualEffectView or it won't have rounded corners.
/// Inspiration: https://stackoverflow.com/a/25475536/129202
class ViewWithRoundedcornersAndShadow: UIView {
private var theShadowLayer: CAShapeLayer?
override func layoutSubviews() {
super.layoutSubviews()
if self.theShadowLayer == nil {
let rounding = CGFloat.init(22.0)
let shadowLayer = CAShapeLayer.init()
self.theShadowLayer = shadowLayer
shadowLayer.path = UIBezierPath.init(roundedRect: bounds, cornerRadius: rounding).cgPath
shadowLayer.fillColor = UIColor.clear.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowRadius = CGFloat.init(3.0)
shadowLayer.shadowOpacity = Float.init(0.2)
shadowLayer.shadowOffset = CGSize.init(width: 0.0, height: 4.0)
self.layer.insertSublayer(shadowLayer, at: 0)
}
}
}
Exact solution for 2020 syntax
import UIKit
class ColorAndShadowButton: UIButton {
override init(frame: CGRect) { super.init(frame: frame), common() }
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder), common() }
private func common() {
// UIButton is tricky: you MUST set the clear bg in bringup; NOT in layout
backgroundColor = .clear
clipsToBounds = false
layer.insertSublayer(colorAndShadow, below: layer)
}
lazy var colorAndShadow: CAShapeLayer = {
let s = CAShapeLayer()
// set your button color HERE (NOT on storyboard)
s.fillColor = UIColor.black.cgColor
// now set your shadow color/values
s.shadowColor = UIColor.red.cgColor
s.shadowOffset = CGSize(width: 0, height: 10)
s.shadowOpacity = 1
s.shadowRadius = 10
// now add the shadow
layer.insertSublayer(s, at: 0)
return s
}()
override func layoutSubviews() {
super.layoutSubviews()
// you MUST layout these two EVERY layout cycle:
colorAndShadow.frame = bounds
colorAndShadow.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
}
}
Note that the very old top answer here is correct but has a critical error
Note that UIButton is unfortunately quite different from UIView in iOS.
Due to a strange behavior in iOS, you must set the background color (which of course must be clear in this case) in initialization, not in layout. You could just set it clear in storyboard (but you usually click it to be some solid color simply so you can see it when working in storyboard.)
In general combos of shadows/rounding are a real pain in iOS. 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
To improve PiterPan's answer and show a real shadow (not just a background with no blur) with a circular button in Swift 3:
override func viewDidLoad() {
super.viewDidLoad()
myButton.layer.masksToBounds = false
myButton.layer.cornerRadius = myButton.frame.height/2
myButton.clipsToBounds = true
}
override func viewDidLayoutSubviews() {
addShadowForRoundedButton(view: self.view, button: myButton, opacity: 0.5)
}
func addShadowForRoundedButton(view: UIView, button: UIButton, opacity: Float = 1) {
let shadowView = UIView()
shadowView.backgroundColor = UIColor.black
shadowView.layer.opacity = opacity
shadowView.layer.shadowRadius = 5
shadowView.layer.shadowOpacity = 0.35
shadowView.layer.shadowOffset = CGSize(width: 0, height: 0)
shadowView.layer.cornerRadius = button.bounds.size.width / 2
shadowView.frame = CGRect(origin: CGPoint(x: button.frame.origin.x, y: button.frame.origin.y), size: CGSize(width: button.bounds.width, height: button.bounds.height))
self.view.addSubview(shadowView)
view.bringSubview(toFront: button)
}
Corner Radius with Shadow
Short and simple way !!!!!
extension CALayer {
func applyCornerRadiusShadow(
color: UIColor = .black,
alpha: Float = 0.5,
x: CGFloat = 0,
y: CGFloat = 2,
blur: CGFloat = 4,
spread: CGFloat = 0,
cornerRadiusValue: CGFloat = 0)
{
cornerRadius = cornerRadiusValue
shadowColor = color.cgColor
shadowOpacity = alpha
shadowOffset = CGSize(width: x, height: y)
shadowRadius = blur / 2.0
if spread == 0 {
shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
shadowPath = UIBezierPath(rect: rect).cgPath
}
}
Use of code
btn.layer.applyCornerRadiusShadow(color: .black,
alpha: 0.38,
x: 0, y: 3,
blur: 10,
spread: 0,
cornerRadiusValue: 24)
No need maskToBound
Please verify clipsToBounds is false.
OUTPUT
Extension to drop shadow and corner radius
extension UIView {
func dropShadow(color: UIColor, opacity: Float = 0.5, offSet: CGSize, shadowRadius: CGFloat = 1, scale: Bool = true, cornerRadius: CGFloat) {
let shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = color.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = offSet
shadowLayer.shadowOpacity = opacity
shadowLayer.shadowRadius = shadowRadius
layer.insertSublayer(shadowLayer, at: 0)
}
}
Here is the solution that will work!
extension UIView {
func applyShadowWithCornerRadius(color:UIColor, opacity:Float, radius: CGFloat, edge:AIEdge, shadowSpace:CGFloat) {
var sizeOffset:CGSize = CGSize.zero
switch edge {
case .Top:
sizeOffset = CGSize(width: 0, height: -shadowSpace)
case .Left:
sizeOffset = CGSize(width: -shadowSpace, height: 0)
case .Bottom:
sizeOffset = CGSize(width: 0, height: shadowSpace)
case .Right:
sizeOffset = CGSize(width: shadowSpace, height: 0)
case .Top_Left:
sizeOffset = CGSize(width: -shadowSpace, height: -shadowSpace)
case .Top_Right:
sizeOffset = CGSize(width: shadowSpace, height: -shadowSpace)
case .Bottom_Left:
sizeOffset = CGSize(width: -shadowSpace, height: shadowSpace)
case .Bottom_Right:
sizeOffset = CGSize(width: shadowSpace, height: shadowSpace)
case .All:
sizeOffset = CGSize(width: 0, height: 0)
case .None:
sizeOffset = CGSize.zero
}
self.layer.cornerRadius = self.frame.size.height / 2
self.layer.masksToBounds = true;
self.layer.shadowColor = color.cgColor
self.layer.shadowOpacity = opacity
self.layer.shadowOffset = sizeOffset
self.layer.shadowRadius = radius
self.layer.masksToBounds = false
self.layer.shadowPath = UIBezierPath(roundedRect:self.bounds, cornerRadius:self.layer.cornerRadius).cgPath
}
}
enum AIEdge:Int {
case
Top,
Left,
Bottom,
Right,
Top_Left,
Top_Right,
Bottom_Left,
Bottom_Right,
All,
None
}
Finally, to apply shadow with corner radius call as per below:
viewRounded.applyShadowWithCornerRadius(color: .gray, opacity: 1, radius: 15, edge: AIEdge.All, shadowSpace: 15)
Result Image
UPDATE: If you don't see the expected output then try calling the extension method from Main Thread, that will work for sure!
DispatchQueue.main.async {
viewRounded.applyShadowWithCornerRadius(color: .gray, opacity: 1, radius: 15, edge: AIEdge.All, shadowSpace: 15)
}
UIButton Extension
Many people have proposed using a custom class of UIButton which is totally fine. Just in case you want an extension, like me, here's one. Written in Swift 5.
extension UIButton {
/// Adds a shadow to the button, with a corner radius
/// - Parameters:
/// - corner: The corner radius to apply to the shadow and button
/// - color: The color of the shaodw
/// - opacity: The opacity of the shadow
/// - offset: The offset of the shadow
/// - radius: The radius of the shadow
func addShadow(corner: CGFloat = 20, color: UIColor = .black, opacity: Float = 0.3, offset: CGSize = CGSize(width: 0, height: 5), radius: CGFloat = 5) {
let shadowLayer = CAShapeLayer()
layer.cornerRadius = corner
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: corner).cgPath
shadowLayer.fillColor = UIColor.clear.cgColor
shadowLayer.shadowColor = color.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = offset
shadowLayer.shadowOpacity = opacity
shadowLayer.shadowRadius = radius
layer.insertSublayer(shadowLayer, at: 0)
}
}
If somebody need add shadows to rounded buttons in Swift 3.0, here is a good method to do it.
func addShadowForRoundedButton(view: UIView, button: UIButton, shadowColor: UIColor, shadowOffset: CGSize, opacity: Float = 1) {
let shadowView = UIView()
shadowView.backgroundColor = shadowColor
shadowView.layer.opacity = opacity
shadowView.layer.cornerRadius = button.bounds.size.width / 2
shadowView.frame = CGRect(origin: CGPoint(x: button.frame.origin.x + shadowOffset.width, y: button.frame.origin.y + shadowOffset.height), size: CGSize(width: button.bouds.width, height: button.bounds.height))
self.view.addSubview(shadowView)
view.bringSubview(toFront: button)
}
Use this method in func viewDidLayoutSubviews() as bellow:
override func viewDidLayoutSubviews() {
addShadowForRoundedButton(view: self.view, button: button, shadowColor: .black, shadowOffset: CGSize(width: 2, height: 2), opacity: 0.5)
}
The effect of this method is:
You can create a protocol and conform it to you UIView, UIButton, Cell or whatever you want like that:
protocol RoundedShadowable: class {
var shadowLayer: CAShapeLayer? { get set }
var layer: CALayer { get }
var bounds: CGRect { get }
}
​
extension RoundedShadowable {
func applyShadowOnce(withCornerRadius cornerRadius: CGFloat, andFillColor fillColor: UIColor) {
if self.shadowLayer == nil {
let shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath
shadowLayer.fillColor = fillColor.cgColor
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0)
shadowLayer.shadowOpacity = 0.2
shadowLayer.shadowRadius = 3
self.layer.insertSublayer(shadowLayer, at: 0)
self.shadowLayer = shadowLayer
}
}
}
​
class RoundShadowView: UIView, RoundedShadowable {
var shadowLayer: CAShapeLayer?
private let cornerRadius: CGFloat
private let fillColor: UIColor
init(cornerRadius: CGFloat, fillColor: UIColor) {
self.cornerRadius = cornerRadius
self.fillColor = fillColor
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.applyShadowOnce(withCornerRadius: self.cornerRadius, andFillColor: self.fillColor)
}
}
​
class RoundShadowButton: UIButton, RoundedShadowable {
var shadowLayer: CAShapeLayer?
private let cornerRadius: CGFloat
private let fillColor: UIColor
init(cornerRadius: CGFloat, fillColor: UIColor) {
self.cornerRadius = cornerRadius
self.fillColor = fillColor
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.applyShadowOnce(withCornerRadius: self.cornerRadius, andFillColor: self.fillColor)
}
}

iOS add / remove shadow from a view

I do not understand how to remove a shadow that was added to a view.
I add to my view in initWithFrame a shadow in this way:
self.layer.borderWidth = 2;
self.layer.borderColor = [UIColor clearColor].CGColor;
self.backgroundColor = [UIColor greenColor];
[self.layer setCornerRadius:8.0f];
CALayer *layer = self.layer;
layer.shadowOffset = CGSizeMake(2, 2);
layer.shadowColor = [[UIColor blackColor] CGColor];
layer.cornerRadius = 8.0f;
layer.shadowRadius = 3.0f;
layer.shadowOpacity = 0.80f;
layer.shadowPath = [[UIBezierPath bezierPathWithRect:layer.bounds] CGPath];
After in the execution of the app I want to remove the shadow from this view. I've tried using:
layer.hidden = YES;
or
self.layer.hidden = YES;
but this hides the view completely, not just the added shadow.
Is there a way to retrieve the added shadow from a view and then hide it?
Thanks!
I guess you could use the shadowOpacity property of your CALayer.
So this should work:
self.layer.shadowOpacity = 0;
See the CALayer's shadowOpacity documentation page
And to show your shadow use:
self.layer.shadowOpacity = 1.0;
Sorry, not sure the correct way, but have you tried changing the properties of the layer shadow? For example, one of these;
layer.shadowOffset = CGSizeMake(0, 0);
layer.shadowColor = [[UIColor clearColor] CGColor];
layer.cornerRadius = 0.0f;
layer.shadowRadius = 0.0f;
layer.shadowOpacity = 0.00f;
Swift 4.2
I am using this in my code for labels and navigation bar.
extension UIView {
func shadow(_ height: Int = 5) {
self.layer.masksToBounds = false
self.layer.shadowRadius = 4
self.layer.shadowOpacity = 1
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowOffset = CGSize(width: 0 , height: height)
}
func removeShadow() {
self.layer.shadowOffset = CGSize(width: 0 , height: 0)
self.layer.shadowColor = UIColor.clear.cgColor
self.layer.cornerRadius = 0.0
self.layer.shadowRadius = 0.0
self.layer.shadowOpacity = 0.0
}
}
I tried the other answers, the only thing that worked for me was to toggle layer.masksToBounds to true/false
lazy var myView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.layer.cornerRadius = 5
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOpacity = 3
view.layer.shadowOffset = .zero
view.layer.shadowRadius = 5
return view
}()
func showShadow() {
myView.layer.masksToBounds = false
}
func hideShadow() {
myView.layer.masksToBounds = true
}
the "layer" that you are trying to make hidden is the layer of the object that you are having a shadow to it's not a visible aspect.. only the objects with in the layer... it's rather confusing to conceptualize anyways, the only way to remove the shadow is to undo what you originally did, which was suggested above, there is no defined property that you can just toggle a bool and make the shadow go away
Swift 5.+
My solution was to add a shadowBackgroundView, which has a removable shadowLayer. In this way I could easyly remove the layer without resetting the shadow properties.
class ViewController: UIViewController {
private let shadowBackgroundView: UIView = {
let view = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.layer.masksToBounds = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(shadowBackgroundView)
// the view you want to add the shadow
let dummyView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
dummyView.backgroundColor = .red
shadowBackgroundView.addSubview(dummyView)
let addShadowButton = UIButton(frame: CGRect(x: 100, y: 300, width: 140, height: 50))
addShadowButton.backgroundColor = .blue
addShadowButton.setTitle("Add Shadow", for: .normal)
addShadowButton.addTarget(self, action: #selector(addShadow), for: .touchUpInside)
let removeShadowButton = UIButton(frame: CGRect(x: 100, y: 450, width: 140, height: 50))
removeShadowButton.backgroundColor = .blue
removeShadowButton.setTitle("Remove Shadow", for: .normal)
removeShadowButton.addTarget(self, action: #selector(removeShadow), for: .touchUpInside)
view.addSubview(addShadowButton)
view.addSubview(removeShadowButton)
}
#objc
func addShadow() {
let shadowLayer = CALayer()
shadowLayer.name = "ShadowLayer"
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowOpacity = 1
shadowLayer.shadowOffset = .zero
shadowLayer.shadowRadius = 10
shadowLayer.shadowPath = UIBezierPath(rect: shadowBackgroundView.bounds).cgPath
// Otherwise the shadow will appear above the dummyView
shadowLayer.zPosition = -1
shadowBackgroundView.layer.addSublayer(shadowLayer)
}
#objc
func removeShadow() {
// Alternatively, you could also create the shadowLayer as a property, so you could call shadowLayer.removeFromSuperLayer()
shadowBackgroundView.layer.sublayers?.first { $0.name == "ShadowLayer" }?.removeFromSuperlayer()
}
}
As a Note, for the UITableViewCell, you wouldnt need to add a shadowBackgroundView, but you could add the shadowLayer directly to cell.view.layer, which serves as the backgroundView for the cell.contentView.
Swift 4.2
Just to add to the other answers, you could change the opacity through animation:
#IBAction func btnDidTap(_ sender: UICustomGestureRecognizer) {
guard let view = sender.view else {return}
if sender.state == .began {
UIView.animate(withDuration: 0.3,
animations: {
view.layer.shadowOpacity = 0
})
} else if sender.state == .ended {
UIView.animate(withDuration: 0.3,
animations: {
view.layer.shadowOpacity = 1
})
}
}

Categories

Resources