Change Button Color on state change - ios

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.

Related

UISearchBar with a white background is impossible?

I really thought it would be easy to set the background color of my UISearchBar's text field to white. But no matter what I try, it always stays offwhite / light gray (#efeff0).
import UIKit
class ViewController: UIViewController {
private let searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Hello World"
view.backgroundColor = #colorLiteral(red: 0.9588784575, green: 0.9528519511, blue: 0.9350754619, alpha: 1)
searchController.searchBar.searchTextField.backgroundColor = .white
navigationItem.searchController = searchController
}
}
How can I make the search bar have a pure white background color? App is iOS 13+, if that helps.
Tiny test project: https://github.com/kevinrenskers/WhiteSearch.
It's possible. Set the background of the search field with a white image.
let size = CGSize(width: searchController.searchBar.frame.size.width - 12, height: searchController.searchBar.frame.size.height - 12)
let backgroundImage = createWhiteBG(size)!
let imageWithCorner = backgroundImage.createImageWithRoundBorder(cornerRadiuos: 10)!
searchController.searchBar.setSearchFieldBackgroundImage(imageWithCorner, for: UIControl.State.normal)
If you don't want to input an image to app. Try this for create one programmatically.
func createWhiteBG(_ frame : CGSize) -> UIImage? {
var rect = CGRect(x: 0, y: 0, width: 0, height: 0)
rect.size = frame
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(UIColor.white.cgColor)
context?.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
extension UIImage {
func createImageWithRoundBorder(cornerRadiuos : CGFloat) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.size, false, scale)
let rect = CGRect(origin:CGPoint(x: 0, y: 0), size: self.size)
let context = UIGraphicsGetCurrentContext()
let path = UIBezierPath(
roundedRect: rect,
cornerRadius: cornerRadiuos
)
context?.beginPath()
context?.addPath(path.cgPath)
context?.closePath()
context?.clip()
self.draw(at: CGPoint.zero)
context?.restoreGState()
path.lineWidth = 1.5
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
Try this ... Change colors and images according to your preference
DispatchQueue.main.async {
searchBar.backgroundImage = UIImage()
for s in searchBar.subviews[0].subviews {
if s is UITextField {
s.layer.borderWidth = 1.0
s.layer.borderColor = UIColor.lightGray.cgColor
}
}
let searchTextField:UITextField = searchBar.subviews[0].subviews.last as? UITextField ?? UITextField()
searchTextField.layer.cornerRadius = 10
searchTextField.textAlignment = NSTextAlignment.left
let image:UIImage = UIImage(named: "search")!
let imageView:UIImageView = UIImageView.init(image: image)
searchTextField.leftView = nil
searchTextField.placeholder = "Search..."
searchTextField.font = UIFont.textFieldText
searchTextField.rightView = imageView
searchTextField.rightViewMode = UITextField.ViewMode.always
}
Here is My complete Custom Search Bar Which you can define the searchbar backgroundColor and TextField background Color
Tested
import Foundation
class SearchBar: UISearchBar {
override init(frame: CGRect) {
super.init(frame: frame)
makeUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
makeUI()
}
private func makeUI( ) {
//SearchBar BackgroundColor
self.backgroundImage = UIImage(color: UIColor.white)
//Border Width
self.layer.borderWidth = 1
//Border Color
self.layer.borderColor = UIColor("DEDEDE")?.cgColor
//Corner Radius
self.layer.cornerRadius = 3
self.layer.masksToBounds = true
//Change Icon
self.setImage(UIImage(named: "search")?
.byResize(to: CGSize(width: 30, height: 30)), for: .search, state: .normal)
if let searchTextField = self.value(forKey: "searchField") as? UISearchTextField {
//TextField Background !!!!!
searchTextField.backgroundColor = UIColor.white
//TextField Font
searchTextField.font = UIFont(name: "Poppins-Regular", size: 21)
searchTextField.textColor = .black
}
}
}

UIButton with cornerRadius, shadow and background for state

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
}
}

Can I change colour/image UISearchBar clear button?

I want to change clear button color to white. I have tried many ways but no luck.:( I also refer the following link. But it does not work for me.
Please find below code which I have tried. I am working on latest ios 11. appreciate any help.
class SearchBar: UISearchBar {
override init(frame: CGRect) {
super.init(frame: frame)
sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedInit()
}
override func awakeFromNib() {
super.awakeFromNib()
sharedInit()
}
private func sharedInit() {
self.placeholder = "Search"
self.setTextColor(color: .white)
self.setTextFieldColor(color: UIColor.hexString("549C64"))
self.setPlaceholderTextColor(color: .white)
self.setSearchImageColor(color: .white)
self.setTextFieldClearButtonColor(color: .white)
if let textfield = self.value(forKey: "searchField") as? UITextField {
if let backgroundview = textfield.subviews.first {
backgroundview.layer.cornerRadius = 18.0;
backgroundview.clipsToBounds = true;
}
//textfield.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width, height: 10.0)
}
//NO LUCK FOR HEIGHT
for subView in self.subviews {
for subsubView in subView.subviews {
if let textField = subsubView as? UITextField {
for subsubView in textField.subviews {
if let btn = subsubView as? UIButton {
print(btn)
}
}
}else if let btn = subsubView as? UIButton {
print(btn)
}
}
if let btn = subView as? UIButton {
print(btn)
}
}
}
}
extension UISearchBar {
private func getViewElement<T>(type: T.Type) -> T? {
let svs = subviews.flatMap { $0.subviews }
guard let element = (svs.filter { $0 is T }).first as? T else { return nil }
return element
}
func getSearchBarTextField() -> UITextField? {
return getViewElement(type: UITextField.self)
}
func getSearchBarButton() -> UIButton? {
return getViewElement(type: UIButton.self)
}
func setTextColor(color: UIColor) {
if let textField = getSearchBarTextField() {
textField.textColor = color
}
}
func setTextFieldColor(color: UIColor) {
if let textField = getViewElement(type: UITextField.self) {
switch searchBarStyle {
case .minimal:
textField.layer.backgroundColor = color.cgColor
textField.layer.cornerRadius = 18.0
if let backgroundview = textField.subviews.first {
backgroundview.layer.cornerRadius = 18.0;
backgroundview.clipsToBounds = true;
}
case .prominent, .default:
textField.backgroundColor = color
}
}
}
func setPlaceholderTextColor(color: UIColor) {
if let textField = getSearchBarTextField() {
textField.attributedPlaceholder = NSAttributedString(string: self.placeholder != nil ? self.placeholder! : "", attributes: [NSAttributedStringKey.foregroundColor: color])
}
}
func setTextFieldClearButtonColor(color: UIColor) {
if let textField = getSearchBarTextField() {
let button = textField.value(forKey: "_clearButton") as! UIButton
if let image = button.imageView?.image {
button.setImage(image.transform(withNewColor: color), for: .normal)
}else{
//button.setImage(#imageLiteral(resourceName: "icon-hotel"), for: .normal)
}
}
if let btn = getSearchBarButton() {
let button = btn.value(forKey: "_clearButton") as! UIButton
if let image = button.imageView?.image {
button.setImage(image.transform(withNewColor: color), for: .normal)
}else{
//button.setImage(#imageLiteral(resourceName: "icon-hotel"), for: .normal)
}
}
}
func setSearchImageColor(color: UIColor) {
if let imageView = getSearchBarTextField()?.leftView as? UIImageView {
imageView.image = imageView.image?.transform(withNewColor: color)
}
}
}
extension UIImage {
func transform(withNewColor color: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let context = UIGraphicsGetCurrentContext()!
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: 1.0, y: -1.0)
context.setBlendMode(.normal)
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
context.clip(to: rect, mask: cgImage!)
color.setFill()
context.fill(rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return newImage
}
}
You can use custom icons in iOS 11:
Code:
searchBar.setImage(UIImage(named: "ic_clear"), for: .clear, state: .normal)
I have found the answer please find below code for the same
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if let searchTextField = searchBar.value(forKey: "searchField") as? UITextField , let clearButton = searchTextField.value(forKey: "_clearButton")as? UIButton {
if let img3 = clearButton.image(for: .highlighted) {
clearButton.isHidden = false
let tintedClearImage = img3.imageWithColor(color1: UIColor.white)
clearButton.setImage(tintedClearImage, for: .normal)
clearButton.setImage(tintedClearImage, for: .highlighted)
}else{
clearButton.isHidden = true
}
}
}
Add following extension for Image color change in your project.
extension UIImage {
func imageWithColor(color1: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
color1.setFill()
let context = UIGraphicsGetCurrentContext()
context?.translateBy(x: 0, y: self.size.height)
context?.scaleBy(x: 1.0, y: -1.0)
context?.setBlendMode(CGBlendMode.normal)
let rect = CGRect(origin: .zero, size: CGSize(width: self.size.width, height: self.size.height))
context?.clip(to: rect, mask: self.cgImage!)
context?.fill(rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}
Here is My solution:
My codes try to change ios textfield clear button color.
But in some IOS versions UIButton for key "_clearButton" return nil
Cuz of it registered in high IOS Versions.
I have tested my extensions it worked in IOS 14, but in IOS 12.4 textfield clear button was not changed color. I debuged and recognize that forKey: "_clearButton" return nil in IOS 12.4
My solution tries get clearbutton image if it is nil use custom image and change color of it
extension UITextField{
var clearButton : UIButton{
return self.value(forKey: "_clearButton") as! UIButton
}
var clearButtonTintColor: UIColor? {
get {
return clearButton.tintColor
}
set {
var image = clearButton.imageView?.image
if image == nil{
image = UIImage(named: "clear_field")//this is custom image
}
image = image?.withRenderingMode(.alwaysTemplate)
clearButton.setImage(image, for: .normal)
clearButton.tintColor = newValue
}
}
}
to change color use this:
textField.clearButtonTintColor = UIColor(rgb: 0x753a3a)
Here is a similiar official clear button image
Update with Swift 5.7
You can add an extension to UIImage to change the tint color of an image.
extension UIImage {
func imageWithColor(color1: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
color1.setFill()
let context = UIGraphicsGetCurrentContext()
context?.translateBy(x: 0, y: self.size.height)
context?.scaleBy(x: 1.0, y: -1.0)
context?.setBlendMode(CGBlendMode.normal)
let rect = CGRect(origin: .zero, size: CGSize(width: self.size.width, height: self.size.height))
context?.clip(to: rect, mask: self.cgImage!)
context?.fill(rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
Then configure the close button with its tint color depending on the Theme color you have(optional).
setImage(UIImage(named:"searchbarClear")?.imageWithColor(color1: ThemeManager.shared.currentTheme.searchBarImageTintColor), for: .clear, state: .normal)
How to change clearButton’s Color?
iOS 11 Solution :
Unfortunately, in iOS 11 we cannot change the clear button’s the rendering mode programmatically(I have no idea why). First set your new clear icon image programmatically and then go to the Assets folder and change your icon’s rendering mode manually.
let clearButton = textFieldInsideSearchBar?.value(forKey: “clearButton”) as! UIButton
clearButton.setImage(UIImage(named: "ic_clear"), for: .normal)
clearButton.tintColor = .white
iOS 10 and Below:
let clearButton = textFieldInsideSearchBar?.value(forKey: “clearButton”) as! UIButton
clearButton.setImage(clearButton.imageView?.image?.withRenderingMode(.alwaysTemplate), for: .normal)
clearButton.tintColor = UIColor.white

Xcode Use PDF Image in Bar Button Item Size Too Big

I have a PDF file in my image assets file in my Xcode project. I'm trying to create a bar button item that uses that PDF image. But whenever I set the image property in Interface Builder to the image it takes over most of my tab bar and removes the title.
I have a left bar button item with the system item stop. So it looks like an X icon. I would like to do the same thing on the right side with my settings PDF image I have.
Is there a way to fix this sizing issue within Interface Builder? If not how can I fix this in code?
Using Code what I do when the image size is too big, I resize the UIImage before rendering/setting on the UIControl. I have this extension on UIImage that helps re-scale an image.
// MARK: - Used to scale UIImages
extension UIImage {
func scaleTo(_ newSize: CGSize) -> UIImage {
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
let newImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage ?? self
}
}
Using this would look like this
let defaultImage = UIImage(named: "someimagenamehere")?
.scaleTo(CGSize(width: 40, height: 40))
self.navigationItem.leftBarButtonItem = UIBarButtonItem(
image: defaultImage,
style: .plain,
target: self,
action: #selector(self.someselectorhere(_:)))
UPDATE: This is what it would look like using #IBDesignable and #IBInspectable
#IBDesignable
class CustomBarButtonItem: UIBarButtonItem {
#IBInspectable
var scaledHeight: CGFloat = 0 {
didSet {
self.image = self.image?.scaleTo(CGSize(width: self.scaledHeight, height: self.scaledWidth))
}
}
#IBInspectable
var scaledWidth: CGFloat = 0 {
didSet {
self.image = self.image?.scaleTo(CGSize(width: self.scaledHeight, height: self.scaledWidth))
}
}
}
The accepted solution is a good approach. However, in my case, I had one item pdf and the rest where png. I faced also the issue of working on a started project and all the icons were different sizes.
I ended up doing:
extension UIBarButtonItem {
convenience init(image: UIImage?, size: CGSize, tintColor: UIColor, target: Any?, action: Selector?) {
let holderView = UIButton()
let button: UIButton = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
holderView.addSubview(button)
button.centerYAnchor.constraint(equalTo: holderView.centerYAnchor).isActive = true
button.centerXAnchor.constraint(equalTo: holderView.centerXAnchor).isActive = true
button.heightAnchor.constraint(equalToConstant: size.height).isActive = true
button.heightAnchor.constraint(equalToConstant: size.width).isActive = true
button.setImage(image?.withRenderingMode(.alwaysTemplate), for: .normal)
button.imageView?.contentMode = .scaleAspectFit
button.tintColor = tintColor
if let target = target, let action = action {
button.addTarget(target, action: action, for: .touchUpInside)
}
self.init(customView: holderView)
}
}
I hope it helps

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