How to create a CKComponentViewAttribute for a method with multiple arguments

292 Views Asked by At

I want to create a component for a UIButton subclass. To setup the button i want to set the image. My problem is I don't know how to make a CKComponentViewAttribute which takes the two arguments needed for UIButton method setImage:forState:.

I tried this:

[CKComponent newWithView:{
  [KGHitTestingButton class],
  {
    {CKComponentActionAttribute(@selector(onMenuTap))},
    {@selector(setImage:forState:), [UIImage imageNamed:@"location_action"]}, @(UIControlStateNormal)},
             }
  } size:{.width = 30, .height = 30}]

But that won't compile. The same with this version:

    {@selector(setImage:forState:), @[[UIImage imageNamed:@"location_action"]}, @(UIControlStateNormal)]},
             }

I read here http://componentkit.org/docs/advanced-views.html that I need to box my inputs into a single object. How do I do that in this case?

Update: I found the following hint here https://github.com/facebook/componentkit/issues/265 :

"CKComponentViewAttribute can be initialized with an arbitrary block. Here's an example:

static const CKComponentViewAttribute titleShadowColorAttribute = {"MyComponent.titleShadowColor", ^(UIButton *button, id value){
  [button setTitleShadowColor:value forState:UIControlStateNormal];
}};

"

And this is how I finally managed to solve my problem:

static const CKComponentViewAttribute imageAttribute = {"LocationActionButton.imageAttribute", ^(UIButton *button, id value){
    [button setImage:value forState:UIControlStateNormal];
}};
CKComponent *locationActionButton = [CKComponent newWithView:{
    [UIButton class],
    {
        {@selector(setBackgroundColor:), [UIColor redColor]},
        {imageAttribute, [UIImage imageNamed:@"location_action"]}
    }
} size:{.width = 30, .height = 30}];

You can do it inline to make it shorter:

CKComponent *locationActionButton = [CKComponent newWithView:{
    [UIButton class],
    {
        {@selector(setBackgroundColor:), [UIColor redColor]},
        {{"LocationActionButton.imageAttribute", ^(UIButton *button, id value){
            [button setImage:value forState:UIControlStateNormal];
        }}, [UIImage imageNamed:@"location_action"]}
    }
} size:{.width = 30, .height = 30}];
2

There are 2 best solutions below

1
On BEST ANSWER

One way you could solve this is to create a private category for the class that passes a collection with the arguments, and then applies then as expected. So something like:

@interface KGHitTestingButton (CKPrivate)
- (void)_ckSetImageforState:(NSArray*)args;
@end
@implementation
- (void)_ckSetImageforState:(NSArray*)args {
    [self setImage:args[0] forState:[args[1] integerValue]];
}
@end

...

[CKComponent newWithView:{
      [KGHitTestingButton class],   
      {
          {@selector(_ckSetImageforState:), @[[UIImage imageNamed:@"location_action"]}, @(UIControlStateNormal)]},
         }
 ...

It's not pretty, but it would work around this limitation. NSDictionary would be an alternative, but would require defining some static keys.

0
On

CKComponentViewAttribute have 2 initializers. One is with an selector, the most common method we use.

CKComponentViewAttribute(SEL setter);
// e.g. ' { @selector(setBackgroundColor:), color} '

The other is

CKComponentViewAttribute(const std::string &ident,
                       void (^app)(id view, id value),
                       void (^unapp)(id view, id value) = nil,
                       void (^upd)(id view, id oldValue, id newValue) = nil) ;

it takes an identifier and 3 blocks to config view at different conditions. Reference more form CKComponentViewAttribute.h, and "CKComponentGestureActions.h" for example.

As in your case:

{ {"setImage",
   ^(UIButton *button, id value) {
     [button setImage:theImageObject forState:UIControlStateNormal]
  },
 @0 // Bogus value, we don't use it.
}