简体   繁体   English

如何倾听UIButton状态的变化?

[英]How listen for UIButton state change?

I'm extending UIButton with generic functionality to change certain appearance attributes based on the displayed title. 我正在扩展具有通用功能的UIButton ,以根据显示的标题更改某些外观属性。

In order to do this, I need to detect and respond to changes in the "state" property. 为此,我需要检测并响应“state”属性的变化。 This is so I make sure the appearance is adjusted properly if the user has set different titles for different states. 这样,如果用户为不同的状态设置了不同的标题,我确保正确调整外观。 I assumed I would need to use some sort of KVO like the following: 我以为我需要使用某种KVO,如下所示:

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

But this does not seem to fire the observeValueForKeyPath:... method for @"state" or @"currentTitle". 但这似乎没有触发@“state”或@“currentTitle”的observeValueForKeyPath:...方法。 I assume this is because UIButton does not implement the KVO pattern for those properties. 我假设这是因为UIButton没有为这些属性实现KVO模式。

I do not want to just listen for clicks. 我不想只听点击。 Those events cause a state change, but are not the only potential causes. 这些事件导致状态改变,但不是唯一的潜在原因。

Does anyone know a way to listen to and respond to state changes of a UIButton? 有没有人知道如何倾听和回应UIButton的状态变化?

Thanks 谢谢


UPDATE UPDATE

Just a note since I've learned a few things in the last couple years ;). 因为我在过去几年中学到了一些东西,所以只是一个注释;)。

I've since talked with some Apple folks who know, and the reason KVO doesn't work on the state property owes to the fact that NONE of UIKit is guaranteed to be KVO compliant. 我已经和一些知道的苹果人谈过了,而且KVO没有在国家财产上工作的原因是因为UIKit的NONE肯定是符合KVO的。 Thought that was worth repeating here--if you are trying to listen to any property of a UIKit framework class, be aware that it may work but is not officially supported and could break on different iOS versions. 在这里值得重复的思考 - 如果你试图听取UIKit框架类的任何属性,请注意它可能有效,但是没有得到官方支持,可能会在不同的iOS版本上中断。

Alright I figured out a solution that works. 好吧,我找到了一个有效的解决方案。 You can listen to the text property of the button's titleLabel. 您可以收听按钮titleLabel的text属性。

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

It seems to get fired twice per change, so you should check to make sure that the values of @"old" and @"new" in the passed change dictionary are different. 它似乎每次更改都会被触发两次,因此您应该检查以确保传递的更改字典中的@“old”和@“new”的值不同。

NOTE: Don't use @"old" and @"new" directly. 注意:不要直接使用@“old”和@“new”。 The constants are NSKeyValueChangeOldKey and NSKeyValueChangeNewKey respectively. 常量分别是NSKeyValueChangeOldKey和NSKeyValueChangeNewKey。

I needed this today, so I wrote this class which does the job: 我今天需要这个,所以我写了这个完成工作的课:

MyButton.h 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 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

You can create it programatically using a UIButton init method, or use it from Interface Builder by adding a normal UIButton to your view and changing the class to MyButton , but you must listen for the UIControlEventStateChanged event programatically. 您可以使用UIButton init方法以编程方式创建它,或者通过向视图添加普通UIButton并将类更改为MyButton ,从Interface Builder中使用它,但您必须以编程方式侦听UIControlEventStateChanged事件。 For example from viewDidLoad in your controller class like this: 例如,来自控制器类中的viewDidLoad ,如下所示:

[self.myButton addTarget:self 
                  action:@selector(myButtonStateChanged:) 
        forControlEvents:UIControlEventStateChanged];
[self addObserver:self 
       forKeyPath:@"state" 
          options:NSKeyValueObservingOptionNew 
          context:nil];

Works fine if you check inside observer 'selected' property 如果您检查内部观察者'选定'属性,则工作正常

-(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];
}

Subclass UIButton, override setState: is what works for me. 子类UIButton,覆盖setState:对我有用。 This is probably not the best way, but I have done it successfully. 这可能不是最好的方法,但我已经成功完成了。

Apologies for the above answer, it was wrong. 对于上述答案道歉,这是错误的。 Should have actually looked at my code. 本来应该看看我的代码。 In my case, I only needed to change the state based on highlight, so I overrode - setHighlight: to change whatever values I needed. 在我的情况下,我只需要根据高亮改变状态,所以我覆盖 - setHighlight:改变我需要的任何值。 YMMV. 因人而异。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM