Reputation: 5785
This question is about Swift 4, Xcode 9.3.1.
The process to change the color of an image button is well documented. That's not the problem.
This question is about WHY the color of an image button resets to the default color if the button happens to be the target inside IBAction
, and what do to about it.
I created a fresh project in Xcode to demonstrate what I'm seeing. This is a project that has an IBAction in the view controller that attempts to change the color of the images on a button. I added three buttons(oneButton
, twoButton
, threeButton
) to the app, and wired them, so each has the following outlets:
Each button has a different image defined. Here's an example:
And here's the ViewController
:
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var oneButton: UIButton!
@IBOutlet weak var twoButton: UIButton!
@IBOutlet weak var threeButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
@IBAction func myAction(sender: UIButton) {
let oneImageView = oneButton.imageView
if sender == oneButton {
print("oneButton")
oneImageView?.setImageColor(color: UIColor.red)
} else if sender == twoButton {
print("twoButton")
oneImageView?.setImageColor(color: UIColor.blue)
} else if sender == threeButton {
print("threeButton")
oneImageView?.setImageColor(color: UIColor.purple)
}
}
}
extension UIImageView {
func setImageColor(color: UIColor) {
let templateImage = self.image?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
self.image = templateImage
self.tintColor = color
}
}
Clicking each icon shows the appropriate text in the output window (oneButton
, twoButton
, threeButton
), depending on which one is clicked. That works fine.
When the app starts, the color of the first image is black (default), as expected.
Clicking twoButton
causes the color on the first image to change to blue, as expected.
Clicking threeButton
causes the color on the first image to change to purple, as expected.
Clicking oneButton
causes the color on the first image to reset to the default (black). This is NOT expected.
I imagine what is happening is that, because the button is currently processing the system event (the touch), the color I'm setting gets wiped-out by some system process.
I changed the code so that rather than Touch Up Inside
, the myAction()
was called upon Touch Down
, and the color of the first image DID turn red! But only while touching... as soon as I released, the color went back to default.
What I would like is to be able to touch oneButton
and have it change to whatever color is in the code, and stay that way, rather than having it reset to the default color.
The code above does set the image rendering mode over and over. It shouldn't be necessary to do that, I realize. But when I didn't do it this way, no colors would be changed when the app ran in the simulator. The code above at least changes the color of the image as long as it's not the item being touched. In the code below, although the appropriate portion of the if/else in myAction()
runs, setting .tintColor
has no effect at all, no matter which item is clicked. I tried with experiment
true and false, to no avail.
class ViewController: UIViewController {
@IBOutlet weak var oneButton: UIButton!
@IBOutlet weak var twoButton: UIButton!
@IBOutlet weak var threeButton: UIButton!
var oneImageView: UIImageView!
var oneImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
oneImageView = oneButton.imageView
oneImage = oneImageView?.image
let experiment = true
if experiment {
oneImageView?.image = UIImage(named: "image")?.withRenderingMode(.alwaysTemplate)
} else {
oneImage.withRenderingMode(.alwaysTemplate)
oneImageView.image = oneImage
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
@IBAction func myAction(sender: UIButton) {
//let oneImageView = oneButton.imageView
let tintColor: UIColor = {
print("switch doesn't work here for some reason")
if sender == oneButton {
print("oneButton")
return .red
} else if sender == twoButton {
print("twoButton")
return .blue
} else if sender == threeButton {
print("threeButton")
return .purple
} else {
return .cyan
}
}()
oneImageView?.tintColor = tintColor
}
}
Upvotes: 4
Views: 5742
Reputation: 5785
The short answer is that the image you expect to apply the color to needs to be designated as a template image. If this designation is made in the image asset's attribute inspector, all the code in the original question will work. Any calls to .withRenderingMode(.alwaysTemplate)
would become unnecessary.
Unless the image is set as a Template Image, the color won't be applied.
The way that currently works (Xcode 9.4, Swift 4.1) is by setting the image to a template image in the GUI Attributes Inspector.
Once this is done, all versions of the code in the original and edited question should work as expected.
Setting the image to a template image in code seems like it should work, but, at least with Xcode 9.4, Swift 4.1, on a simulator, it does not have permanence; the first user touch resets it.
In the code below, the .withRenderingMode(.alwaysTemplate)
does cause the icon to become a template, but as soon as the user touches the icon the first time, it becomes default rendering again. This is true in the simulated iPhone (didn't test on a physical device).
override func viewDidLoad() {
super.viewDidLoad()
oneImageView = oneButton.imageView
// NOT PERMANENT
oneImageView?.image = UIImage(named: "image")?.withRenderingMode(.alwaysTemplate)
}
Upvotes: 5
Reputation: 58029
You don't need to set the image again and again, just set the image once as template and change the tint color later. Also, it's cleaner to use switch
instead of multiple if
s.
override func viewDidLoad() {
super.viewDidLoad()
oneImageView?.image = UIImage(named: "image").withRenderingMode(.alwaysTemplate)
}
@IBAction func myAction(_ sender: UIButton) {
let tintColor: UIColor = {
switch sender {
case oneButton: return .red
case twoButton: return .blue
case threeButton: return .purple
default: return .clear
}
}()
oneImageView?.tintColor = tintColor
}
Upvotes: 1