Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIButton with IB_DESIGNABLE throws runtime attribute warning and does not render in Interface Builder

I'm running Xcode 6.1 and I have been using IB_DESIGNABLE with IBInspectable for quite a few projects already but all of the sudden it just doesn't work anymore. I have created subclassed buttons that arrange the image and the title vertically centred above each other and enable the user to set a border width and color through IB with IBInspectable.

The following warning is logged and there is no preview available of my code in drawRect:

warning: IB Designables: Ignoring user defined runtime attribute for key path "spacingBetweenLabelAndImage" on instance of "UIButton". Hit an exception when attempting to set its value: [<UIButton 0x7f9278591260> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key spacingBetweenLabelAndImage.

Still, runtime it renders like I intended it to render and it also honours that same custom spacing I've added through IB.

Here's the code of the menubutton that rearranges the button and the title:

#import "HamburgerButton.h"

IB_DESIGNABLE
@interface HamburgerImageButton : HamburgerButton

@property IBInspectable CGFloat spacingBetweenLabelAndImage;

@end

Implementation:

#import "HamburgerImageButton.h"

@implementation HamburgerImageButton

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];

    CGSize imageSize = self.imageView.frame.size;
    CGSize titleSize = self.titleLabel.frame.size;

    // Move the label left and the image right by half the width
    CGFloat leftInset = titleSize.width / 2;
    CGFloat rightInset = imageSize.width / 2;

    CGFloat halfSpacing = self.spacingBetweenLabelAndImage == 0 ? 0 : self.spacingBetweenLabelAndImage / 2;

    CGFloat topInset = imageSize.height / 2 + halfSpacing;
    CGFloat bottomInset = titleSize.height / 2 + halfSpacing;

    UIEdgeInsets imageInsets = UIEdgeInsetsMake(-bottomInset, leftInset, bottomInset, -leftInset);
    UIEdgeInsets titleInsets = UIEdgeInsetsMake(topInset, -rightInset, -topInset, rightInset);
    self.imageEdgeInsets = imageInsets;
    self.titleEdgeInsets = titleInsets;
}

@end

You've probably noticed it inherits HamburgerButton. This basic hamburger button does not have an image and it only draws the border around the button. This basic hamburger button has exactly the same problem: it does not draw it's border in drawRect in IB and it has the same type of errors. Here's that code for sake of completeness:

#import <UIKit/UIKit.h>

IB_DESIGNABLE
@interface HamburgerButton : UIButton

@property IBInspectable CGFloat borderWidth;
@property IBInspectable UIColor *borderColor;

@end

Implementation:

#import "HamburgerButton.h"
#import <QuartzCore/QuartzCore.h>

@implementation HamburgerButton

- (void)copyInspectables {
    self.layer.borderWidth = self.borderWidth;
    self.layer.borderColor = self.borderColor.CGColor;
}

- (void)drawRect:(CGRect)rect {
    [self copyInspectables];
}

@end

I don't really understand why it just throws a warning and nothing else. I didn't really change what I did. I've checked the storyboard, it's iOS 7 and up, meant to run in Xcode 6 (latest).

It complains about not being able to find that value on UIButton and that's a bit weird because I've subclassed it.


Update: So I changed everything around and it worked. Now it craps out again, without changing anything else. I think there's a bug in Xcode 6.1... :/

like image 741
Lucas van Dongen Avatar asked Oct 30 '14 14:10

Lucas van Dongen


5 Answers

I have been working extensively with Live Views since it's introductions and like a lot of new features in Xcode initially, it has some flaws. Still I like it a lot and I hope that it will be improved in newer versions.

Cause:

Live Views work with @property properties that get a special IB_Designable tag that will give the UI class extra attributes it can approach via User Defined Runtime Attributes that will be set through the Attributes Inspector. You will see that all those properties also exist in the Identity Inspector. The root cause of the problem is that it cannot map those User Defined Runtime Attributes anymore to exposed properties.

As soon as you open up a Storyboard or xib that uses IB_Designable, Xcode will recompile the screen at every change if you have "Automatically Refresh Views" turned on. Since your code influences your UI as well this doesn't have to be a change in Interface Builder per se.

With simpler projects on faster machines this works pretty well the majority of the time. With larger projects that also have more IB_Designable the CPU simply doesn't have enough capacity to keep up with you and you will get a sort of time-out error while rendering the UI. This is the cause of the "warning: IB Designables: Ignoring user defined runtime attribute for key path "spacingBetweenLabelAndImage" on instance of "UIButton". Hit an exception when attempting to set its value: [ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key spacingBetweenLabelAndImage." error.

It will also not update your UI anymore with your custom code. This is not a problem when you run it though, because at run time it will reflect those changes.

Another cause can be that your code simply doesn't compile when it tries to compile in the background. This is inevitable when you are working on code so that triggers it frequently as well.

Solution:

We need to make sure that your custom view class compiles again so the properties can be set successfully via Interface Builder

  • While in a storyboard, go to "Editor" in the top bar menu
  • deselect "Automatically refresh views" if it was still turned on
  • Close all your storyboard tabs
  • Rebuild the project
  • Fix all build errors if any
  • Re-open your storyboard.
  • Go to "Editor" again and click "Refresh All Views"

The warnings should be gone and if it worked before you will see your custom view code again as well.

After a comment below by sunkas: it's always a very good idea to clean your solution thoroughly in case of any weird problems that persist.

like image 79
Lucas van Dongen Avatar answered Nov 15 '22 21:11

Lucas van Dongen


How I personally fixed this:

In the identity inspector in User Defined Runtime Attributes I noticed that I had an old IB_Inspectable element that I had deleted from my code, but it was still in there. Removing it with the - button got rid of this error.

identityinspector

like image 24
HannahCarney Avatar answered Nov 15 '22 22:11

HannahCarney


Try changing your "CGFloat" declaration for spacingBetweenLabelAndImage into "NSNumber".

As far as I know, you can't call "setValue:" on a C-type variable.

Changing the declaration of "spacingBetwenLabelAndImage" to NSNumber would also require you to change a bit of code too:

CGFloat halfSpacing = self.spacingBetweenLabelAndImage == 0 ? 0 : self.spacingBetweenLabelAndImage / 2;

might become something like:

CGFloat halfSpacing = (self.spacingBetweenLabelAndImage ? ([self.spacingBetweenLabelAndImage floatValue] / 2) : 0.0);

(I'm not 100% certain if this is a perfect translation of the first line, but I wanted to show how to get a floatValue out of a NSNumber)

like image 42
Michael Dautermann Avatar answered Nov 15 '22 21:11

Michael Dautermann


I had a similar problem, but I actually removed the property from the class and Interface Builder was still complaining about the missing property, tough.

Tried many things, like cleaning, rebuilding... But nothing worked!

What really solved was right clicking in the Storyboard file, selecting "Open As" and then "Source code". This opened the storyboard XML content in the editor and I was able to look for references to the missing property until I found the section:

<userDefinedRuntimeAttributes>
    <userDefinedRuntimeAttribute... keyPath="My Missing Property Name">
    ...
</userDefinedRuntimeAttributes>

After removing the whole section, I saved the file and refreshed the view in Interface Builder. Now all warnings are gone!

like image 2
rmartinsjr Avatar answered Nov 15 '22 21:11

rmartinsjr


I used the method Departamento B provided but it didn't really work. However, I fixed my problem by 1. Selecting the buggy views from story board 2. Clicking (Editor - Debug selected view) Hope this helps

like image 1
Martin Z Avatar answered Nov 15 '22 22:11

Martin Z