UIButton with cornerRadius, shadow and background for state - ios

This seems to be a tricky problem I cannot find the solution for.
I need a UIButton with:
rounded corners,
background color for state,
drop shadow.
I can achieve the the corner radius and background color for state subclassing a UIButton and setting the parameters:
// The button is set to "type: Custom" in the interface builder.
// 1. rounded corners
self.layer.cornerRadius = 10.0
// 2. Color for state
self.backgroundColor = UIColor.orange // for state .normal
self.setBackgroundColor(color: UIColor.red, forState: .highlighted)
// 3. Add the shadow
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 3)
self.layer.shadowOpacity = 0.3
self.layer.shadowRadius = 6.0
self.layer.masksToBounds = false
// I'm also using an extension to be able to set the background color
extension UIButton {
func setBackgroundColor(color: UIColor, forState: UIControl.State) {
UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
if let context = UIGraphicsGetCurrentContext() {
context.setFillColor(color.cgColor)
context.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
let colorImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.setBackgroundImage(colorImage, for: forState)
}
}
}
The problem:
Once the button is rendered, seems fine, has the desired background color, rounded corners and the shadow. But once is pressed the setBackgroundImage(image, for: forState) launches and gets rid of the radius, so the button is displayed as a square with the desired color and shadow.
Is there a way to preserve the radius when the button is pressed (.highlighted)?
I did try the solutions from this post (for example) but none consider the setBackgroundImage(image, for: forState). I cannot find anything that works...

If you just want to change the background color for .normal and .highlighted - that is, you don't need a background image - you can override var isHighlighted to handle the color changes.
Here is an example UIButton subclass. It is marked #IBDesignable and has normalBackground and highlightBackground colors marked as #IBInspectable so you can set them in Storyboard / Interface Builder:
#IBDesignable
class HighlightButton: UIButton {
#IBInspectable
var normalBackground: UIColor = .clear {
didSet {
backgroundColor = self.normalBackground
}
}
#IBInspectable
var highlightBackground: UIColor = .clear
override open var isHighlighted: Bool {
didSet {
backgroundColor = isHighlighted ? highlightBackground : normalBackground
}
}
func setBackgroundColor(_ c: UIColor, forState: UIControl.State) -> Void {
if forState == UIControl.State.normal {
normalBackground = c
} else if forState == UIControl.State.highlighted {
highlightBackground = c
} else {
// implement other states as desired
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// 1. rounded corners
self.layer.cornerRadius = 10.0
// 2. Default Colors for state
self.setBackgroundColor(.orange, forState: .normal)
self.setBackgroundColor(.red, forState: .highlighted)
// 3. Add the shadow
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 3)
self.layer.shadowOpacity = 0.3
self.layer.shadowRadius = 6.0
self.layer.masksToBounds = false
}
}

Related

Swift Backgroung image not stretching to bounds

I'm currently trying to add a background image to my navigation bar but the background image itself is not stretching to fill the bounds of the specified space (the pink button should be covering the blue square or atleast getting close to same size).
How do I get the background image to stretch/ fill the space?
Screenshot:
How I add the button:
let newsButton = UIButton(type: .custom)
newsButton.translatesAutoresizingMaskIntoConstraints = false
newsButton.backgroundColor = .blue
newsButton.setTitle(NSLocalizedString("News", comment: "News button"), for: .normal)
newsButton.layer.cornerRadius = 7
newsButton.titleLabel?.font = .systemFont(ofSize: 20)
newsButton.addTarget(self, action: #selector(onClick(_:)), for: .touchUpInside)
if let image = UIImage(named: "pink_button") {
newsButton.setBackgroundImage(image, for: .normal)
}
NSLayoutConstraint.activate([
newsButton.widthAnchor.constraint(equalToConstant: 128),
newsButton.heightAnchor.constraint(equalToConstant: 43)
])
navigationItem.titleView = newsButton
Your image has a lot of transparent space around the "red" shape.
If you replace it with this image (I trimmed out the alpha areas):
It will look like this:
As an alternative to using a "stretched image" you could use this custom view (that draws your shape) and embed the button as a subview:
class MyBottomShadowView: UIView {
var radius: CGFloat = 8
var primaryColor: UIColor = .red
var shadowColor: UIColor = .gray
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
// background color needs to be .clear
self.backgroundColor = .clear
}
override func draw(_ rect: CGRect) {
super.draw(rect)
var r: CGRect!
var pth: UIBezierPath!
// if rounded rect for "bottom shadow line"
// goes all the way to the top, we'll get
// anti-alias artifacts at the top corners
// so, we'll make it slightly smaller and
// move it down from the top
r = bounds.insetBy(dx: 0, dy: 2).offsetBy(dx: 0, dy: 2)
pth = UIBezierPath(roundedRect: r, cornerRadius: radius)
shadowColor.setFill()
pth.fill()
// "filled" rounded rect should be
// 2-points shorter than height
r = bounds
r.size.height -= 2.0
pth = UIBezierPath(roundedRect: r, cornerRadius: radius)
primaryColor.setFill()
pth.fill()
}
}
Your setup then becomes:
let newsButton = UIButton(type: .custom)
newsButton.translatesAutoresizingMaskIntoConstraints = false
newsButton.setTitleColor(.white, for: .normal)
newsButton.setTitleColor(.lightGray, for: .highlighted)
newsButton.setTitle(NSLocalizedString("News", comment: "News button"), for: .normal)
newsButton.titleLabel?.font = .systemFont(ofSize: 20)
// set button background to clear
newsButton.backgroundColor = .clear
newsButton.addTarget(self, action: #selector(onClick(_:)), for: .touchUpInside)
// create "bottom shadow view"
let shadView = MyBottomShadowView()
// set radius, primary and shadow colors as desired
shadView.radius = 12
shadView.primaryColor = UIColor(red: 1.00, green: 0.25, blue: 0.40, alpha: 1.0)
shadView.shadowColor = UIColor(red: 0.65, green: 0.20, blue: 0.30, alpha: 1.0)
shadView.translatesAutoresizingMaskIntoConstraints = false
shadView.addSubview(newsButton)
NSLayoutConstraint.activate([
shadView.widthAnchor.constraint(equalToConstant: 128),
shadView.heightAnchor.constraint(equalToConstant: 43),
newsButton.topAnchor.constraint(equalTo: shadView.topAnchor),
newsButton.leadingAnchor.constraint(equalTo: shadView.leadingAnchor),
newsButton.trailingAnchor.constraint(equalTo: shadView.trailingAnchor),
newsButton.bottomAnchor.constraint(equalTo: shadView.bottomAnchor),
])
navigationItem.titleView = shadView
And it looks like this:

UIButton with background image, rounded corners and a shadow

I'm trying to create a UIButton with rounded corners, a background image and a shadow. Before adding the shadow, everything works fine.
But after adding shadow values, the shadow doesn't show up. Obviously due to clipsToBounds property value being set to true. If I remove that, it looks like this.
Since I need the corner radius as well, I cannot have the clipsToBounds be false.
This is my code.
class CustomButton: UIButton {
var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
clipsToBounds = true
}
}
var shadowRadius: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowRadius = newValue
}
}
var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
var shadowOffset: CGSize {
get {
return layer.shadowOffset
}
set {
layer.shadowOffset = newValue
}
}
var shadowColor: UIColor? {
get {
if let color = layer.shadowColor {
return UIColor(cgColor: color)
}
return nil
}
set {
if let color = newValue {
layer.shadowColor = color.cgColor
} else {
layer.shadowColor = nil
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
private lazy var button: CustomButton = {
let button = CustomButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setBackgroundImage(UIImage(named: "Rectangle"), for: .normal)
button.setTitleColor(.white, for: .normal)
button.setTitle("Sign Up", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
button.cornerRadius = 20
button.shadowColor = .systemGreen
button.shadowRadius = 10
button.shadowOpacity = 1
button.shadowOffset = CGSize(width: 0, height: 0)
return button
}()
Is there a workaround to have both the shadow and the corner radius?
Demo project
You can do it via adding shadow and background image with different layer.
First, if you don't need the properties, remove all and modify your CustomButton implementation just like below (modify as your need):
class CustomButton: UIButton {
private let cornerRadius: CGFloat = 20
private var imageLayer: CALayer!
private var shadowLayer: CALayer!
override func draw(_ rect: CGRect) {
addShadowsLayers(rect)
}
private func addShadowsLayers(_ rect: CGRect) {
// Add Image
if self.imageLayer == nil {
let imageLayer = CALayer()
imageLayer.frame = rect
imageLayer.contents = UIImage(named: "Rectangle")?.cgImage
imageLayer.cornerRadius = cornerRadius
imageLayer.masksToBounds = true
layer.insertSublayer(imageLayer, at: 0)
self.imageLayer = imageLayer
}
// Set the shadow
if self.shadowLayer == nil {
let shadowLayer = CALayer()
shadowLayer.masksToBounds = false
shadowLayer.shadowColor = UIColor.systemGreen.cgColor
shadowLayer.shadowOffset = .zero
shadowLayer.shadowOpacity = 1
shadowLayer.shadowRadius = 10
shadowLayer.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
layer.insertSublayer(shadowLayer, at: 0)
self.shadowLayer = shadowLayer
}
}
}
And initialize your button like below:
private lazy var button: CustomButton = {
let button = CustomButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(.white, for: .normal)
button.setTitle("Sign Up", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
return button
}()
You need to use two separate views for shadow and image. I can't find any solution to set image, shadow, and corner radius using the same button layer.
Make button's corner radius(clipsToBounds=true) rounded and set the image on it.
Take a shadow view under the button with proper shadow radius and offset.
You can add view.layer.masksToBounds = false
This will disable clipping for sublayer
https://developer.apple.com/documentation/quartzcore/calayer/1410896-maskstobounds

Swift: Target not getting called on a subclassed UIButton

CustomButton.swift
class CustomButton: UIButton {
override func draw(_ rect: CGRect) {
//drawing code
}
}
ViewController.swift
let testCustom = CustomButton()
testCustom.draw(CGRect(x: 0, y: 0, width: 0, height: 0))
testCustom.isUserInteractionEnabled = true
testCustom.addTarget(self, action: #selector(Start(_:)), for: .touchUpInside)
self.view.addSubview(testCustom)
#objc func Start(_ sender: CustomButton) {
print("pressed start")
}
The button appears on screen but the function does not get called upon pressing the button. Any ideas why?
I also tried the function and addTarget code within the CustomButton.swift but couldn't get that to trigger either.
Thanks for any help!
The following is a simple example of how to instantiate a UIButton subclass in a view controller (UIViewController). It's tested under Swift 4.2.
// Subclassing UIButton //
import UIKit
class MyButton: UIButton {
var tintColor0: UIColor!
var tintColor1: UIColor!
var borderColor: UIColor!
var backColor: UIColor!
var cornerRadius: CGFloat!
required init(frame: CGRect, tintColor0: UIColor, tintColor1: UIColor, borderColor: UIColor, backColor: UIColor, cornerRadius: CGFloat, titleString: String) {
super.init(frame: frame)
self.tintColor0 = tintColor0
self.tintColor1 = tintColor1
self.borderColor = borderColor
self.backColor = backColor
self.cornerRadius = cornerRadius
self.setTitle(titleString, for: .normal)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
self.setTitleColor(tintColor0, for: .normal)
self.setTitleColor(tintColor1, for: .highlighted)
self.layer.borderColor = borderColor.cgColor
self.layer.cornerRadius = cornerRadius
self.layer.borderWidth = 1.0
self.layer.backgroundColor = backColor.cgColor
}
}
// View controller //
import UIKit
class ViewController: UIViewController {
// MARK: - Variables
// MARK: - IBOutlet
// MARK: - IBAction
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
let buttonRect = CGRect(x: 20.0, y: 160.0, width: 100.0, height: 32.0)
let myButton = MyButton(frame: buttonRect, tintColor0: UIColor.black, tintColor1: UIColor.gray, borderColor: UIColor.orange, backColor: UIColor.white, cornerRadius: 8.0, titleString: "Hello")
myButton.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
view.addSubview(myButton)
}
#objc func buttonTapped(_ sender: UIButton) {
print("Hello!?")
}
}
#MuhammadWaqasBhati made a good point asking about the frame.
I was using addSublayer to draw the path I had created onto the screen. My mistake here was that I was setting values in the draw() function and adding the CAShapeLayer with addSublayer, but a frame for the button wasn't being set.
Even though the layer that is drawn is a sublayer of the button, it appears at the coordinates and dimensions provided for the layer, without any relation to the frame of its "parent" button.
The frame of the button could be (0, 0, 0, 0) or (0, 0, 100, 100) and the image drawn in addSublayer could still be at (250, 200, 75, 80) so that the visible image would be in one spot of the screen, but the actual button is in an unrelated spot to what is visible in its sublayer.

Change Button Color on state change

I want for the buttons to have white background and blue title when highlighted. I have an extension of UIButton to set its background color.
extension UIButton {
func setBackgroundColor(color: UIColor, forState: UIControlState) {
UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
UIGraphicsGetCurrentContext()!.setFillColor(color.cgColor)
UIGraphicsGetCurrentContext()!.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
let colorImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.setBackgroundImage(colorImage, for: forState)
self.clipsToBounds = true
}
}
and in the next function, I set up a particular button.
private func stylizingButton(button: UIButton){
button.layer.borderWidth = 2
button.layer.borderColor = textColor.cgColor
button.layer.cornerRadius = 8
button.setTitleColor(textColor, for: .normal)
button.setTitleColor(backgroundColor, for: .highlighted)
button.setBackgroundColor(color: .white, forState: .highlighted)
}
When I change the background color of the button to black, the result is some dark blue color. It is like the screen background color and the button's color are mixing.
Create a custom class for your button and handle your color changing properties on state like below.
class MyButton: UIButton {
fileprivate var titleColorNormal: UIColor = .white
fileprivate var titleColorHighlighted: UIColor = .blue
fileprivate var backgroundColorNormal: UIColor = .blue
fileprivate var backgroundColorHighlighted: UIColor = .white
override var isHighlighted: Bool {
willSet(newValue){
if newValue {
self.setTitleColor(titleColorHighlighted, for: state)
self.backgroundColor = backgroundColorHighlighted
}else {
self.setTitleColor(titleColorNormal, for: state)
self.backgroundColor = backgroundColorNormal
}
}
}
}
Either make image for the whole size, or make it stretchable, so it can fill the whole background:
extension UIButton {
func setBackgroundColor(color: UIColor, for state: UIControlState) {
let rect = CGRect(origin: CGPoint(x: 0, y:0), size: CGSize(width: 1, height: 1))
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()!
context.setFillColor(color.cgColor)
context.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let insets = UIEdgeInsetsMake(0, 0, 0, 0)
let stretchable = image!.resizableImage(withCapInsets: insets, resizingMode: .tile)
self.setBackgroundImage(stretchable, for: state)
}
}
I had the same problem with the mixing colors in highlighted state but didn't want to create a custom class. I found out that you can simply change the button type from "System" to "Custom". Then you can use the functions for setting colors by state. The colors will be displayed as defined.
You can change the button type in interface builder.

How do I set UIButton background color forState: UIControlState.Highlighted in Swift

I can set the background color for a button but I can't work out how to set the background color for UIControlState.Highlighted. Is it even possible? or do I need to go down the setBackgroundImage path?
If anyone stops by, another way to go maybe more easily if it is something you need more than once... I wrote a short extension for UIButton, it works just fine:
for Swift 3
extension UIButton {
func setBackgroundColor(color: UIColor, forState: UIControlState) {
UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), color.CGColor)
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRect(x: 0, y: 0, width: 1, height: 1))
let colorImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.setBackgroundImage(colorImage, forState: forState)
}
}
for Swift 4
extension UIButton {
func setBackgroundColor(color: UIColor, forState: UIControl.State) {
self.clipsToBounds = true // add this to maintain corner radius
UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
if let context = UIGraphicsGetCurrentContext() {
context.setFillColor(color.cgColor)
context.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
let colorImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.setBackgroundImage(colorImage, for: forState)
}
}
}
You use it just like setBackgroundImage:
yourButton.setBackgroundColor(color: UIColor.white, forState: UIControl.State.highlighted)
Syntax changes to #winterized extension for Swift 3+ syntax
extension UIButton {
func setBackgroundColor(color: UIColor, forState: UIControlState) {
UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
UIGraphicsGetCurrentContext()!.setFillColor(color.cgColor)
UIGraphicsGetCurrentContext()!.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
let colorImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.setBackgroundImage(colorImage, for: forState)
}}
Below will be one way to go. Two IBActions. One to control background color when depressing a button, one on release.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
#IBAction func buttonClicked(sender: AnyObject) { //Touch Up Inside action
button.backgroundColor = UIColor.whiteColor()
}
#IBAction func buttonReleased(sender: AnyObject) { //Touch Down action
button.backgroundColor = UIColor.blueColor()
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
When you look at the autocomplete options for your button after adding a period, you can set a background color, but not for specified state. You can only set background images. Now of course if you are married to doing it this way instead of using the method I show above, you could load an image of the desired color as the background image using the setbackgroundImageForState property.
Swift 4+ compatibility for the accepted answer :
extension UIButton {
/// Sets the background color to use for the specified button state.
func setBackgroundColor(color: UIColor, forState: UIControlState) {
let minimumSize: CGSize = CGSize(width: 1.0, height: 1.0)
UIGraphicsBeginImageContext(minimumSize)
if let context = UIGraphicsGetCurrentContext() {
context.setFillColor(color.cgColor)
context.fill(CGRect(origin: .zero, size: minimumSize))
}
let colorImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.clipsToBounds = true
self.setBackgroundImage(colorImage, for: forState)
}
}
Compatible SwiftLint and fix the bug of broken auto layout / corner radius.
Swift 4 Version of this solution:
extension UIButton {
func setBackgroundColor(_ color: UIColor, for state: UIControlState) {
UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
UIGraphicsGetCurrentContext()!.setFillColor(color.cgColor)
UIGraphicsGetCurrentContext()!.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
let colorImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
setBackgroundImage(colorImage, for: state)
}
}
Seems nobody here has mentioned using Key Value Observation yet, but it's another approach.
A reason for doing so instead of picking the other answers here is you don't need to go creating new images all the time nor be concerned with secondary effects of assigning images to buttons (e.g. cornerRadius effects).
But you'll need to create a class for the observer, who would be responsible for storing the different background colours and applying them in the observeValue() method.
public class ButtonHighlighterObserver: NSObject {
var observedButton:UIButton? = nil
var backgroundColor: UIColor = UIColor.white
var backgroundHighlightColor: UIColor = UIColor.gray
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// Perform background color changes when highlight state change is observed
if keyPath == "highlighted", object as? UIButton === observedButton {
observedButton!.backgroundColor = observedButton!.isHighlighted ? self.backgroundHighlightColor : self.backgroundColor
}
}
}
Then all you need to do is manage addObserver / removeObserver during operation:
// Add observer to button's highlighted value
button.addObserver(anObserver, forKeyPath: "highlighted", options: [.new], context: nil)
anObserver.observedButton = button
// ...
// And at deinit time, be sure you remove the observer again
anObserver.observedButton?.removeObserver(item, forKeyPath: "highlighted")
anObserver.observedButton = nil
Update Swift 4
extension UIButton {
func setBackgroundColor(color: UIColor, forState: UIControlState) {
UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
UIGraphicsGetCurrentContext()!.setFillColor(color.cgColor)
UIGraphicsGetCurrentContext()!.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
let colorImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.setBackgroundImage(colorImage, for: forState)
}
Event
Button Action
Show Touch On Hightlight
Why not?
#implementation UIButton (Color)
- (void) setBackgroundColor:(UIColor*)color forState:(UIControlState)state
{
[self setBackgroundImage:[UIImage.alloc initWithCIImage:[CIImage imageWithColor:[CIColor colorWithCGColor:color.CGColor]]] forState:state];
}
#end
You can override isHighlighted and changed the background color when the isHighlighted is set.
Example: TextButton.Swift
import UIKit
class TextButton: UIButton {
private var text: String = "Submit" {
didSet{
setText()
}
}
var hightlightedColor : UIColor = UIColor(red: 50/255, green: 50/255, blue: 50/255, alpha: 1)
var background :UIColor = .black {
didSet{
self.backgroundColor = background
}
}
override var isHighlighted: Bool {
didSet {
self.backgroundColor = self.isHighlighted ? hightlightedColor : background
}
}
// MARK: - Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
sharedLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedLayout()
}
// MARK: - Method
private func setText() {
self.setTitle(text, for: .normal)
}
private func sharedLayout() {
self.setTitle(text, for: .normal)
self.backgroundColor = self.isHighlighted ? .green : background
self.layer.cornerRadius = 8
}
}
Usages:
let nextBtn = TextButton()
in Swift 5
For those who don't want to use colored background to beat the selected state
Simply you can beat the problem by using #Selector & if statement to change the UIButton colors for each state individually easily
For Example:
override func viewDidLoad() {
super.viewDidLoad()
//to reset the button color to its original color ( optionally )
self.myButtonOutlet.backgroundColor = UIColor.white
}
#IBOutlet weak var myButtonOutlet: UIButton!{
didSet{ // Button selector and image here
self.myButtonOutlet.setImage(UIImage(systemName: ""), for: UIControl.State.normal)
self.myButtonOutlet.setImage(UIImage(systemName: "checkmark"), for: UIControl.State.selected)
self.myButtonOutlet.addTarget(self, action: #selector(tappedButton), for: UIControl.Event.touchUpInside)
}
}
#objc func tappedButton() { // Colors selection is here
if self.myButtonOutlet.isSelected == true {
self.myButtonOutlet.isSelected = false
self.myButtonOutlet.backgroundColor = UIColor.white
} else {
self.myButtonOutlet.isSelected = true
self.myButtonOutlet.backgroundColor = UIColor.black
self.myButtonOutlet.tintColor00 = UIColor.white
}
}
If using storyboard or xib use #IBInspectable by adding extension for UIButton
import Foundation
import UIKit
import ObjectiveC
// Declare a global var to produce a unique address as the assoc object handle
var highlightedColorHandle: UInt8 = 0
extension UIButton {
func setBackgroundColor(_ color: UIColor, for state: UIControl.State) {
self.setBackgroundImage(UIImage.init(color: color), for: state)
}
#IBInspectable
var highlightedBackground: UIColor? {
get {
if let color = objc_getAssociatedObject(self, &highlightedColorHandle) as? UIColor {
return color
}
return nil
}
set {
if let color = newValue {
self.setBackgroundColor(color, for: .highlighted)
objc_setAssociatedObject(self, &highlightedColorHandle, color, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
} else {
self.setBackgroundImage(nil, for: .highlighted)
objc_setAssociatedObject(self, &highlightedColorHandle, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}
Use in Storyboard or Xib like
Swift 5
In my case I needed something else, because the option with the extension and setBackgroundColor function doesn't work properly in case when we have different background button colors for dark/light mode and when we change traitCollection.userInterfaceStyle (dark/light mode). So I used custom implementation of UIButton class and override properties isHighlighted and isEnabled :
import UIKit
class CustomButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
self.isEnabled = isEnabled
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var isHighlighted: Bool {
didSet {
self.backgroundColor = isHighlighted ? UIColor.blue : UIColor.cyan
}
}
override var isEnabled: Bool {
didSet {
self.backgroundColor = isEnabled ? UIColor.cyan : UIColor.red
}
}
}

Resources