1

I want to disable/enable all UIViews in a IBOutletCollection. However the UIViews differ in class, so I can't call the setEnabled directly.

Then I thought I would use the performSelector method to do it, however I can only send an Object as parameter.

I read both on this site and on other sites that I could just use [NSNumber numberWithBool YES/NO], however the enabled state doesn't change when sending a NSNumber with either bool YES or NO.

I got the disabled part to work by using nil, however I couldn't find a way to set it them enabled:

-(void) setControlsState: (BOOL) enabled

{
    for(UIView *subview in controls)
    {
        NSNumber *boolObject = enabled? [NSNumber numberWithBool: YES]: nil;
        if([subview respondsToSelector: @selector(setEnabled:)])
        {
            [subview performSelector: @selector(setEnabled:) withObject: boolObject];
        }
        else if([subview respondsToSelector: @selector(setEditable:)])
        {
            [subview performSelector: @selector(setEditable:) withObject: boolObject];
        }
        subview.alpha = enabled? 1: 0.5;
    }
}

Where controls is a IBOutletCollection consisting of UISliders, UIButtons, UITextViews and UITextfields. (@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *controls;)

Note: The UITextViews works fine wit the above code, it is only the other type of UIViews, which uses setEnabled.

Tyilo
  • 28,998
  • 40
  • 113
  • 198

2 Answers2

1

Is there a specific reason your not using userInteractionEnabled which disallows touch events?

-(void) setControlsState: (BOOL) enabled
{
    for(UIView *aView in controls)
    {
        if ([aView isKindOfClass:[UIView class]]){
            aView.userInteractionEnabled = enabled;
            aView.alpha = (enabled)?1.0:0.5;// Be mindful of this it doesn't seem to respect group opacity. i.e. sliders look funny.
        }
    }
}

If there is you can simply cast the aView pointer after checking it's class, like so: (Of course you'll have to enumerate through all the classes you use)

-(void) setControlsState: (BOOL) enabled
{
    for(UIView *aView in controls)
    {
        if ([aView isKindOfClass:[UISlider class]]){
            [(UISlider *)aView setEnabled:enabled];
        }
        if ([aView isKindOfClass:[UITextView class]]){
            [(UITextView *)aView setEditable:enabled];
        }
        // and so forth
    }
}
NJones
  • 27,139
  • 8
  • 70
  • 88
  • Cool. I edited with a revision of your code, if you in fact couldn't use `userInteractionEnabled`. Which I guess is just for future reference. – NJones Jan 23 '12 at 22:14
0

I came up against this problem myself yesterday. In my case, setting userInteractionEnabled wasn't sufficient, because I wanted the controls to appear greyed out, not just stop responding to touch events. I also had some UIView subclasses with custom enabled/disabled behavior, and I didn't want to have to enumerate all classes as @NJones suggested in case I add more controls in the future.

As the OP noted, using NSNumber does not work. The solution is to use NSInvocation as explained in TomSwift's answer to this question. I decided to wrap this in a convenience function:

void XYPerformSelectorWithBool(id obj, SEL selector, BOOL boolean)
{
    NSMethodSignature *signature = [[obj class] instanceMethodSignatureForSelector:selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setSelector:selector];
    [invocation setArgument:&boolean atIndex:2];
    [invocation invokeWithTarget:obj];
}

Then disabling all controls is as simple as:

for (UIView *view in controls)
    if ([view respondsToSelector:@selector(setEnabled:)])
        XYPerformSelectorWithBool(view, @selector(setEnabled:), NO);
Community
  • 1
  • 1
deltacrux
  • 1,186
  • 11
  • 18