如何防止 UIButton 的图像颜色重置为默认值?

How can I keep the image color of UIButton from resetting to default?

这个问题是关于Swift4,Xcode9.3.1.

更改图像按钮颜色的过程有详细记录。这不是问题。

这个问题是关于 如果按钮恰好是 IBAction 中的目标,为什么图像按钮的颜色会重置为默认颜色,以及如何处理关于它。

我在 Xcode 中创建了一个新项目来演示我所看到的。这是一个在视图控制器中有一个 IBAction 的项目,它试图改变按钮上图像的颜色。我在应用程序中添加了三个按钮(oneButtontwoButtonthreeButton),并连接了它们,因此每个按钮都有以下插座:

每个按钮都定义了不同的图像。这是一个例子:

这里是 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
  }
}

单击每个图标会在输出 window(oneButtontwoButtonthreeButton)中显示相应的文本,具体取决于单击的是哪个图标。效果不错。

当应用程序启动时,第一张图片的颜色是黑色(默认),正如预期的那样。

单击 twoButton 会使第一张图片的颜色按预期变为蓝色。

单击 threeButton 会使第一张图片的颜色按预期变为紫色。

单击 oneButton 会使第一个图像的颜色重置为默认值(黑色)。这是 预期的。

我想这是怎么回事,因为按钮当前正在处理系统事件(触摸),所以我设置的颜色被某些系统进程擦掉了。

我更改了代码,因此在 Touch Down 上调用了 myAction() 而不是 Touch Up Inside,第一张图片的颜色确实变成了红色!但是只有在触摸的时候……一松开,颜色就恢复默认了。

我想要的是能够触摸 oneButton 并将其更改为代码中的任何颜色,并保持这种状态,而不是将其重置为默认颜色。

添加/编辑

上面的代码确实一遍又一遍地设置图像渲染模式。我意识到,不应该 这样做。但是当我不这样做时,在模拟器中应用程序 运行 时不会更改任何颜色。只要不是被触摸的项目,上面的代码至少会改变图像的颜色。在下面的代码中,尽管 myAction() 中 if/else 的适当部分运行,设置 .tintColor 根本没有效果,无论单击哪个项目。我尝试了 experiment true 和 false,但无济于事。

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

您无需反复设置图片,只需将图片设置一次作为模板,稍后更改色调即可。此外,使用 switch 而不是多个 if 更简洁。

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
}

设置模板图片

简短的回答是,您希望应用颜色的图像需要指定为模板图像。如果在图像资产的属性检查器中进行此指定,则原始问题中的所有代码都将起作用。对 .withRenderingMode(.alwaysTemplate) 的任何调用都将变得不必要。

在属性检查器中

除非图像设置为 模板图像,否则不会应用颜色。

当前 的工作方式(Xcode 9.4,Swift 4.1)是在 GUI 属性检查器中将图像设置为模板图像。

完成此操作后,原始问题和编辑过的问题中的所有代码版本都应按预期工作。

在代码中

在代码中将图像设置为模板图像似乎应该有效,但是,至少在 Xcode 9.4、Swift 4.1 上一个模拟器,它没有永久性;第一次用户触摸重置它。

在下面的代码中,.withRenderingMode(.alwaysTemplate) 确实使图标变成了模板,但是 一旦用户第一次触摸图标,它就会再次变成默认呈现。 在模拟中是这样iPhone(没有在物理设备上测试)

override func viewDidLoad() {
  super.viewDidLoad()
  oneImageView = oneButton.imageView
  // NOT PERMANENT
  oneImageView?.image = UIImage(named:   "image")?.withRenderingMode(.alwaysTemplate) 
}