当UIButton处于高亮状态时,如何更改其背景颜色?

274

在我的应用程序中,某个时刻我有一个高亮的UIButton(例如当用户手指放在按钮上时),我需要在按钮处于高亮状态时更改背景颜色(即当用户手指仍然放在按钮上时)。

我尝试了以下方法:

_button.backgroundColor = [UIColor redColor];

但是它没有起作用。颜色仍然保持不变。当按钮未被突出显示时,我尝试了同样的代码,它可以正常工作。我还尝试在更改颜色后调用-setNeedsDisplay,但没有任何效果。

如何强制更改按钮的背景色?


如何强制更改按钮的背景色?

请查看此帖子 https://somethingaboutios.wordpress.com/2016/02/09/uibutton-backgroundcolor-for-uicontrolstateselected/ 和此库 https://github.com/GabrielMassana/ButtonBackgroundColor-iOS。 - Gabriel.Massana
32个回答

445
你可以重写 UIButton 的 setHighlighted 方法。
Objective-C
- (void)setHighlighted:(BOOL)highlighted {
    [super setHighlighted:highlighted];

    if (highlighted) {
        self.backgroundColor = UIColorFromRGB(0x387038);
    } else {
        self.backgroundColor = UIColorFromRGB(0x5bb75b);
    }
}

Swift 3.0和Swift 4.1

override open var isHighlighted: Bool {
    didSet {
        super.isHighlighted = isHighlighted
        backgroundColor = isHighlighted ? UIColor.black : UIColor.white
    }
}

5
一个新手问题,你会在哪里子类化那个按钮方法?如果我在一个名为ConversionViewController的视图控制器中有一个按钮,当按钮被按下或高亮时,如何设置按钮以更改背景颜色?我需要在ConversionViewController中子类化setHighlighted吗? - Beanno1116
3
假设您正在使用一个子类,您可以添加两个UIColor属性,例如normalBackground和highlightedBackground,然后根据需要将self.backgroundColor分配为normalBackground或highlightedBackground。不要忘记添加一个初始化方法,以方便使用,例如initWithBackground:highlightedBackground:。 - SK.
2
不错的解决方案,只有一个建议:backgroundColor = isHighlighted ? .lightGray : .white - Fantini
3
为什么没有人提到 setter 方法仅在您点击按钮时才被调用,而不是在初始布局期间调用!因此,默认情况下,在您触摸按钮之前没有颜色。所以要使其起作用,您还需要在一开始的某个地方明确调用 isHighlighted = false(例如,在初始化时)。 - topolog
1
那么,对于Objective-C来说,我们必须子类化UIButton才能实现这种效果,对吗? - Zhou Haibo
显示剩余6条评论

307

不确定这是否完全解决了您的问题,或者是否符合您的一般开发需求,但我建议首先尝试在touchDown事件中更改按钮的背景颜色。

选项1:

您需要捕获两个事件,UIControlEventTouchDown用于当用户按下按钮时,UIControlEventTouchUpInside和UIControlEventTouchUpOutside用于当他们释放按钮以使其返回到正常状态。

UIButton *myButton =  [UIButton buttonWithType:UIButtonTypeCustom];
[myButton setFrame:CGRectMake(10.0f, 10.0f, 100.0f, 20.f)];
[myButton setBackgroundColor:[UIColor blueColor]];
[myButton setTitle:@"click me:" forState:UIControlStateNormal];
[myButton setTitle:@"changed" forState:UIControlStateHighlighted];
[myButton addTarget:self action:@selector(buttonHighlight:) forControlEvents:UIControlEventTouchDown];
[myButton addTarget:self action:@selector(buttonNormal:) forControlEvents:UIControlEventTouchUpInside];

选项2:

返回一张使用您想要的高亮颜色制作的图像。这也可以是一个类别。

+ (UIImage *)imageWithColor:(UIColor *)color {
   CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
   UIGraphicsBeginImageContext(rect.size);
   CGContextRef context = UIGraphicsGetCurrentContext();

   CGContextSetFillColorWithColor(context, [color CGColor]);
   CGContextFillRect(context, rect);

   UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
   UIGraphicsEndImageContext();

   return image;
}

然后改变按钮的高亮状态:

[myButton setBackgroundImage:[self imageWithColor:[UIColor greenColor]] forState:UIControlStateHighlighted];

3
将UIControlEventTouchUpOutside和UIControlEventTouchCancel事件添加到buttonHighlight事件列表中,它将始终起作用。 - Evgen Bodunov
28
如果你使用layer.cornerRadius并选择选项#2,那么你需要确保将clipsToBounds设置为true,这样图片的角落才会被圆角化。 - Sky
3
如果有人经过并需要用Swift得到答案:https://dev59.com/z18d5IYBdhLWcg3woDYZ#30604658 - winterized
将imageWithColor方法放入单例中,在许多项目中多次使用它。非常有帮助。非常感谢。 - felixwcf
这是一个简单的CocoaPod,它可以使用颜色生成图像,并且还允许您创建具有圆角和颜色的可调整大小的图像:https://github.com/mxcl/UIImageWithColor - mxcl
显示剩余4条评论

100
不需要覆盖计算属性 highlighted,您可以使用属性观察器来触发背景颜色的变化:

无需覆盖计算属性highlighted。您可以使用属性观察器来触发背景颜色更改:

override var highlighted: Bool {
    didSet {
        backgroundColor = highlighted ? UIColor.lightGrayColor() : UIColor.whiteColor()
    }
}

Swift 4

override open var isHighlighted: Bool {
    didSet {
        backgroundColor = isHighlighted ? UIColor.lightGray : UIColor.white
    }
}

1
我从未使用过这样的功能。你可以解释一下这是放在哪里吗?是放在IBAction的buttonPress函数中还是放在viewDidLoad中? - Dave G
如果我有多个具有不同颜色的UIButtons呢? - Slavcho
7
通过点击“文件->新建->文件->Cocoa Touch Class”,并将其设置为“UIButton的子类”,您可以创建一个新的UIButton子类。以“CustomButton”为例,这将成为文件名和类名。在该文件中,加入上面显示的override var highlighted代码。最后一步,通过进入属性页面,在“自定义类”下拉框中选择“CustomButton”,将UIButton设置为使用这个CustomButton子类。原本灰色字体的“UIButton”将变为下拉列表中的选项。选择它,按钮现在就是子类化的了。 - James Toomey
1
为什么没有人提到,setter 只有在你点击按钮时才会被调用,而不是在初始布局期间!因此,默认情况下,在你触摸按钮之前是没有颜色的。 - topolog
1
所以为了使其正常工作,您还需要在某个地方显式调用 isHighlighted = false(例如在初始化时)。 - topolog

55

一个在Swift中方便的通用扩展:

extension UIButton {
    private func imageWithColor(color: UIColor) -> UIImage {
        let rect = CGRectMake(0.0, 0.0, 1.0, 1.0)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()

        CGContextSetFillColorWithColor(context, color.CGColor)
        CGContextFillRect(context, rect)

        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image
    }

    func setBackgroundColor(color: UIColor, forUIControlState state: UIControlState) {
        self.setBackgroundImage(imageWithColor(color), forState: state)
    }
}

Swift 3.0

extension UIButton {
    private func imageWithColor(color: UIColor) -> UIImage? {
        let rect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()

        context?.setFillColor(color.cgColor)
        context?.fill(rect)

        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image
    }

    func setBackgroundColor(_ color: UIColor, for state: UIControlState) {
        self.setBackgroundImage(imageWithColor(color: color), for: state)
    }
}

48

在Swift中,你可以覆盖突出显示(或选定)属性的访问器,而不是覆盖setHighlighted方法。

override var highlighted: Bool {
        get {
            return super.highlighted
        }
        set {
            if newValue {
                backgroundColor = UIColor.blackColor()
            }
            else {
                backgroundColor = UIColor.whiteColor()
            }
            super.highlighted = newValue
        }
    }

这个完全可行,但我很困惑你是如何找出来的?据我所知,这些参数在文档或UIButton.h中都没有提到。 - shimizu
1
这是Swift语法,模拟Objective-C中覆盖setHighlighted行为。在此处查看有关计算属性的文档:https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html - Jake Hall
11
在Swift中,您可以使用didSet。 - Dam
1
我已经添加了一个带有属性观察器的示例:https://dev59.com/-2Uq5IYBdhLWcg3waPpC#29186375。 - Aleksejs Mjaliks
我认为@shimizu想问的是你怎么知道highlighted是UIButton上的一个属性。答案是它是UIControl上的一个属性,而UIButton从中继承了该属性。 - Adam Johns

29

覆盖高亮变量。 添加@IBInspectable可以使您在Storyboard中编辑突出显示的背景颜色,这也很方便。

class BackgroundHighlightedButton: UIButton {
    @IBInspectable var highlightedBackgroundColor :UIColor?
    @IBInspectable var nonHighlightedBackgroundColor :UIColor?
    override var highlighted :Bool {
        get {
            return super.highlighted
        }
        set {
            if newValue {
                self.backgroundColor = highlightedBackgroundColor
            }
            else {
                self.backgroundColor = nonHighlightedBackgroundColor
            }
            super.highlighted = newValue
        }
    }
}

28

使用不需要子类化的Swift 3+解决方案。

extension UIButton {
  func setBackgroundColor(_ color: UIColor, for state: UIControlState) {
    let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
    UIGraphicsBeginImageContext(rect.size)
    color.setFill()
    UIRectFill(rect)
    let colorImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    setBackgroundImage(colorImage, for: state)
  }
}

通过这个扩展,管理不同状态的颜色变得更加容易,并且在没有提供高亮颜色的情况下会自动淡化正常颜色。

button.setBackgroundColor(.red, for: .normal)

1
不错,这在 Swift 5 中也非常好用。 - Joshua
2
我非常感激这个答案,因为它正是 API 缺少的东西。它类似于现有的 setTitle(for:)。在我看来,它应该成为被接受的答案。 - biomiker

25

一个更加简洁的解决方案(基于@aleksejs-mjaliks的回答):

Swift 3/4+:

override var isHighlighted: Bool {
    didSet {
        backgroundColor = isHighlighted ? .lightGray : .white
    }
}

Swift 2:

override var highlighted: Bool {
    didSet {
        backgroundColor = highlighted ? UIColor.lightGrayColor() : UIColor.whiteColor()
    }
}

如果您不想覆盖,这是更新版的@timur-bernikowich的答案(Swift 4.2):

extension UIButton {
  func setBackgroundColor(_ color: UIColor, forState controlState: UIControl.State) {
    let colorImage = UIGraphicsImageRenderer(size: CGSize(width: 1, height: 1)).image { _ in
      color.setFill()
      UIBezierPath(rect: CGRect(x: 0, y: 0, width: 1, height: 1)).fill()
    }
    setBackgroundImage(colorImage, for: controlState)
  }
}

@FedericoZanetello 这将覆盖您应用程序中所有按钮的isHighlighted,我认为这不是一个好的解决方案。我会选择Timur的答案。 - Usama bin Attique

14

使用Swift 3+语法的UIButton扩展:

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

使用方法如下:

YourButton.setBackgroundColor(color: UIColor.white, forState: .highlighted)
原回答: https://dev59.com/z18d5IYBdhLWcg3woDYZ#30604658

10

这里是用Swift编写的一种方法,使用UIButton扩展添加一个IBInspectable,称为highlightedBackgroundColor。类似于子类化,但不需要创建一个子类。

private var HighlightedBackgroundColorKey = 0
private var NormalBackgroundColorKey = 0

extension UIButton {

    @IBInspectable var highlightedBackgroundColor: UIColor? {
        get {
            return objc_getAssociatedObject(self, &HighlightedBackgroundColorKey) as? UIColor
        }

        set(newValue) {
            objc_setAssociatedObject(self,
                &HighlightedBackgroundColorKey, newValue, UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }

    private var normalBackgroundColor: UIColor? {
        get {
            return objc_getAssociatedObject(self, &NormalBackgroundColorKey) as? UIColor
        }

        set(newValue) {
            objc_setAssociatedObject(self,
                &NormalBackgroundColorKey, newValue, UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }

    override public var backgroundColor: UIColor? {
        didSet {
            if !highlighted {
                normalBackgroundColor = backgroundColor
            }
        }
    }

    override public var highlighted: Bool {
        didSet {
            if let highlightedBackgroundColor = self.highlightedBackgroundColor {
                if highlighted {
                    backgroundColor = highlightedBackgroundColor
                } else {
                    backgroundColor = normalBackgroundColor
                }
            }
        }
    }
}

希望这可以帮到你。


1
对于Swift 2.0,您需要更新调用objc_setAssociatedObject的方式,使用枚举:objc_setAssociatedObject(self, &NormalBackgroundColorKey, newValue, .OBJC_ASSOCIATION_RETAIN)。 - Eli Burke
如果您想将所有内容保留在Storyboard中,那么在Swift中绝对是最好的方法。 - davidethell
1
我更喜欢使用子类而不是扩展,因为这会影响整个应用程序。 - Hossam Ghareeb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接