如何监听UIButton状态的变化?

25

我正在扩展UIButton的通用功能,以根据显示的标题更改某些外观属性。

为了实现这一点,我需要检测和响应“状态”属性的更改。这样我就可以确保如果用户为不同的状态设置了不同的标题,外观会被适当地调整。我假设我需要使用类似以下代码的KVO:

[self addObserver:self 
       forKeyPath:@"state" 
          options:NSKeyValueObservingOptionNew 
          context:nil];

然而,这似乎无法激发对@"state"或@"currentTitle"的observeValueForKeyPath:... 方法。我猜这是因为UIButton未实现这些属性的KVO模式。

我不想只监听点击事件。那些事件会导致状态改变,但并不是唯一的潜在原因。

有谁知道一种监听和响应UIButton状态改变的方法吗?

谢谢


更新

只是一个注意事项,因为我在过去几年中学到了一些东西 ;)

我后来与一些了解情况的苹果人交谈,KVO不适用于state属性的原因在于UIKit中没有一个被保证符合KVO规范。认为这值得在此重申 - 如果您尝试监听UIKit框架类的任何属性,请注意它可能有效,但不受官方支持,并且可能在不同的iOS版本上出现问题。


抱歉挖出这个旧帖子。我想知道UIKit不支持KVO是否是因为无法通过App Store验证的原因? - MaxouMask
@Maskime - 我不认为除非你的使用导致应用程序中出现明显的错误,否则他们会否决该应用程序。 - DougW
嗯,据我所知,您无法在“state”上设置KVO,因为“state”没有改变,它只返回“UIControl”的私有变量。 - dimpiax
4个回答

11

好的,我找到了一个可行的解决方案。你可以监听按钮的titleLabel的text属性。

[self.titleLabel addObserver:self 
                  forKeyPath:@"text" 
                     options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld 
                     context:nil];

似乎每次更改都会被触发两次,因此您应该检查传递的更改字典中@"old"和@"new"的值是否不同。

注意:不要直接使用@"old"和@"new"。常量分别是NSKeyValueChangeOldKey和NSKeyValueChangeNewKey。


如果不同状态下文本相同怎么办?即使该属性仍然被重新设置为相同的内容,我认为您不能保证在不同的iOS版本中实现该行为,因为在某些状态更改情况下,实现可能不会设置文本。我编写了一个类并在另一个答案中发布了它 - 希望对某人有用 :) - jhabbott
@jhabbott - 当然,但那样你就改变了我所问的问题。我特别想根据标题文本来改变外观。如果标题文本实际上没有改变,那就没有影响。话虽如此,如果有人关心任何状态变化,你的答案可能是正确的选择——只是对我的需求来说有点过于繁重。 - DougW
2
这让我感到困惑:( 我可以在问题标题中看到“How listen for UIButton state change?”,但正确的答案是“您可以侦听文本属性”...好吧...但在我的特定情况下,不能依赖于标题被更改:( 如果无法使用KVO监听它,那么我想看到相应的答案,或者至少下面的答案更有意义。 - igrek

9
今天我需要这个功能,所以我写了这个类来实现: MyButton.h
#import <UIKit/UIKit.h>

// UIControlEventStateChanged uses the first bit from the UIControlEventApplicationReserved group
#define UIControlEventStateChanged  (1 << 24)

@interface MyButton : UIButton
@end

MyButton.m

#import "MyButton.h"

#pragma mark - Private interface
@interface MyButton ()
- (void)checkStateChangedAndSendActions;
@end

#pragma mark - Main class
@implementation MyButton
{
    // Prior state is used to compare the state before
    // and after calls that are likely to change the
    // state. It is an ivar rather than a local in each
    // method so that if one of the methods calls another,
    // the state-changed actions only get called once.
    UIControlState  _priorState;
}

- (void)setEnabled:(BOOL)enabled
{
    _priorState = self.state;
    [super setEnabled:enabled];
    [self checkStateChangedAndSendActions];
}

- (void)setSelected:(BOOL)selected
{
    _priorState = self.state;
    [super setSelected:selected];
    [self checkStateChangedAndSendActions];
}

- (void)setHighlighted:(BOOL)highlighted
{
    _priorState = self.state;
    [super setHighlighted:highlighted];
    [self checkStateChangedAndSendActions];
}

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
    _priorState = self.state;
    [super touchesBegan:touches withEvent:event];
    [self checkStateChangedAndSendActions];
}

- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
    _priorState = self.state;
    [super touchesMoved:touches withEvent:event];
    [self checkStateChangedAndSendActions];
}

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
    _priorState = self.state;
    [super touchesEnded:touches withEvent:event];
    [self checkStateChangedAndSendActions];
}

- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
{
    _priorState = self.state;
    [super touchesCancelled:touches withEvent:event];
    [self checkStateChangedAndSendActions];
}

#pragma mark - Private interface implementation
- (void)checkStateChangedAndSendActions
{
    if(self.state != _priorState)
    {
        _priorState = self.state;
        [self sendActionsForControlEvents:UIControlEventStateChanged];
    }
}

@end

您可以使用UIButtoninit方法,以编程方式创建它,或者通过在视图中添加一个普通的UIButton并将类更改为MyButton从Interface Builder中使用它,但必须以编程方式监听UIControlEventStateChanged事件。例如,在控制器类的viewDidLoad中像这样:
[self.myButton addTarget:self 
                  action:@selector(myButtonStateChanged:) 
        forControlEvents:UIControlEventStateChanged];

7
我喜欢这个解决方案,但将自定义事件命名为 UIControlEventStateChanged,让人误以为它是框架的一部分,会令人感到困惑。 - Aleks N.
这更像是一种黑客行为。 - pronebird
1
是的 - 这是一个hack。有时候你必须做一些hacky的事情来解决提供的框架中的不足之处。这让我很难过。如果有一种非hacky的方法来实现这个,请发表一个答案,我会很感兴趣 :) - jhabbott

2
[self addObserver:self 
       forKeyPath:@"state" 
          options:NSKeyValueObservingOptionNew 
          context:nil];

如果您检查观察器“selected”属性内部,则可以正常工作。

-(void)observeValueForKeyPath:(NSString *)keyPath  
                     ofObject:(id)object 
                       change:(NSDictionary *)change 
                      context:(void *)context
{
    if ([keyPath isEqualToString:@"selected"])
    {
        [self.img setImage:self.selected ? self.activeImg : self.inactiveImg];
    }
    else
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
}

-2

子类化UIButton,重写setState: 对我来说是有效的方法。这可能不是最好的方式,但我已经成功地完成了。

对以上回答表示歉意,它是错误的。应该实际查看我的代码。在我的情况下,我只需要根据高亮状态改变状态,所以我重写了-setHighlight: 来改变我需要的任何值。你的情况可能有所不同。


5
刚试过了,不起作用。UIControl的文档也确认了:“此属性只读 - 没有对应的setter方法。” - DougW
你不应该覆盖那些你不应该覆盖的方法。 - pronebird

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