I'd like to create @IBInspectable element as you see at the picture below :

enter image description here

my idea is to use something like enum as type for @IBInspectable, but it looks like it's not the case, any ideas how to implement element like this ?

EDIT:

It looks like @IBInspectable supports only these types :

  • Int
  • CGFloat
  • Double
  • String
  • Bool
  • CGPoint
  • CGSize
  • CGRect
  • UIColor
  • UIImage

bummer


Solution 1:

That's not possible (for now). You can only use those types that you see in User Defined Runtime Attributes section.

From Apple's doc:

You can attach the IBInspectable attribute to any property in a class declaration, class extension, or category for any type that’s supported by the Interface Builder defined runtime attributes: boolean, integer or floating point number, string, localized string, rectangle, point, size, color, range, and nil.

Solution 2:

Another work-around for this is to alter how an enumeration property appears to interface builder. For example:

#if TARGET_INTERFACE_BUILDER
@property (nonatomic, assign) IBInspectable NSInteger fontWeight;
#else
@property (nonatomic, assign) FontWeight fontWeight;
#endif

This assumes an enum called FontWeight. It relies on the fact that enums and their raw integer values can be used somewhat interchangeably in Objective-C. After doing this you are able to specify an integer in Interface builder for the property which is not ideal, but works, and retains a small amount of type safety when using the same property programatically.

This is a better alternative than declaring a separate integer property because you don't need to write extra logic to handle a second integer property which could also be used to accomplish the same thing.

However, this does not work with Swift because we're not able to implicitly cast from an integer to an enum. Any thoughts on solving that would be appreciated.

Solution 3:

I do this using a Inspectable NSInteger value and override the setter to allow it to set the enum. This has the limitation of not using a popup list and if you change your enum values, then the interface options will not update to match.

Example.

In Header File:

typedef NS_ENUM(NSInteger, LabelStyle)
{
    LabelStyleContent = 0, //Default to content label
    LabelStyleHeader,
};

...

@property LabelStyle labelStyle;
@property (nonatomic, setter=setLabelAsInt:) IBInspectable NSInteger labelStyleLink;

In the implementation file:

- (void)setLabelAsInt:(NSInteger)value
{
    self.labelStyle = (LabelStyle)value;
}

You could optionally add some logic in there to ensure that it is being set to a valid value

Solution 4:

Sikhapol is correct, enums are not yet supported also not in xCode 9. I believe the safest approach is to use enums as strings and implement a "shadow" (private) IBInspectable var. Here is an example of a BarBtnPaintCode item which represents a barbutton item that can be styled with a custom icon (that is done using PaintCode) right inside Interface Builder (swift 4).

In interface build you just enter the string (identical to the enum value), which keeps it clear (if you are entering numbers nobody knows what they mean)

class BarBtnPaintCode: BarBtnPaintCodeBase {

    enum TypeOfButton: String {
        case cancel
        case ok
        case done
        case edit
        case scanQr
        //values used for tracking if wrong input is used
        case uninitializedLoadedFromStoryboard
        case unknown
    }

    var typeOfButton = TypeOfButton.uninitializedLoadedFromStoryboard

    @IBInspectable private var type : String {
        set {
            typeOfButton = TypeOfButton(rawValue: newValue) ?? .unknown
            setup()
        }
        get {
            return typeOfButton.rawValue
        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    init(typeOfButton: TypeOfButton, title: String? = nil, target: AnyObject?, action: Selector) {
        super.init()
        self.typeOfButton = typeOfButton
        setup()
        self.target = target
        self.action = action
        self.title  = title
    }

    override func setup() {
        //same for all
        setTitleTextAttributes([NSAttributedStringKey.font : UIFont.defaultFont(size: 15)],for: UIControlState.normal)
        //depending on the type
        switch typeOfButton {
        case .cancel  :
            title = nil
            image = PaintCode.imageOfBarbtn_cancel(language: currentVisibleLanguage)
        case .ok      :
            title = nil
            image = PaintCode.imageOfBarbtn_ok(language: currentVisibleLanguage)
        case .done    :
            title = nil
            image = PaintCode.imageOfBarbtn_done(language: currentVisibleLanguage)
        case .edit    :
            title = nil
            image = PaintCode.imageOfBarbtn_edit(language: currentVisibleLanguage)
        case .uninitializedLoadedFromStoryboard :
            title = nil
            image = PaintCode.imageOfBarbtn_unknown
            break
        case .unknown:
            log.error("BarBtnPaintCode used with unrecognized type")
            title = nil
            image = PaintCode.imageOfBarbtn_unknown
            break
        }

    }

}