ioscomponentkit

How to create a CKComponentViewAttribute for a method with multiple arguments


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

Solution

  • 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.