18

So I am implementing a custom "chooser" toolbar, like the iOS equivalent of a radio button set (UISegmentedControl). Just a horizontal bar divided into options.

To do this, I created a subclass of UIControl called SegmentedControl and implemented custom drawing. However, with such a view, I need the option to set what the available options are. I could have just accessed the view from the controller's viewDidLoad() and set those there, but I like using the interface builder for that kind of stuff.

So I discovered this wonderful thing called "User Defined Runtime Attributes." I created a String attribute with a key buttonValues and set a value (this is a simple Male/Female chooser so I went with "Male|Female"). I found out that you can access these values using the function self.valueForKey() and pass in the key. I made a parser to turn that string into an array and then added functionality for the drawRect() function to use the array to set up the buttons.

When I ran the app, I got an error about "Key Value Coding-compliance."

So I looked that up, and I found out that the class has to have backing variables to store the attributes. So fine, I added an instance variable called buttonValues and initialized it to "". Now the app runs fine but the value comes out empty from the self.valueForKey() function. I looked up tutorials on how to set up user defined runtime attributes but they don't go into enough detail. They talk about Key Value Coding-compliance like it's something I should just know.

I would like to know exactly what I must do for this to work properly, in gory detail.

Nate Cook
  • 92,417
  • 32
  • 217
  • 178
jchitel
  • 2,999
  • 4
  • 36
  • 49

1 Answers1

27

For your purposes you can use either user-defined runtime attributes or expose your class and properties as editable in Interface Builder. Either way, you'll want to declare your properties as implicitly unwrapped Optional variables -- IBOutlets are created the same way. If you need to make other changes once your properties have a value, give them didSet property observers. The properties will be at their default value during initialization (or nil if no default was set) and set when the view is added to a superview.

User Defined Runtime Attributes

This works more or less like you've described above -- here's the simplest version:

class LabeledView : UIView {
    var viewLabel: String! {
    didSet {
        println("didSet viewLabel, viewLabel = \(self.viewLabel)")
    }
    }

    init(coder aDecoder: NSCoder!)  {
        super.init(coder: aDecoder)
        println("init with coder, viewLabel = \(self.viewLabel)")
    }
}

Then set the viewLabel attribute to "Hello" on the view in your storyboard, like so:

Xcode panel showing user-defined runtime attributes

When you build & run, the console will show that the property is being set correctly:

init with coder, viewLabel = nil
didSet viewLabel, viewLabel = Hello

@IBDesignable & @IBInspectable

This gives your custom view a much nicer interface in IB -- set the @IBDesignable attribute on your class and @IBInspectable attributes on each property. Here's the same class:

@IBDesignable class LabeledView : UIView {
    @IBInspectable var viewLabel: String!

    init(coder aDecoder: NSCoder!)  {
        super.init(coder: aDecoder)
    }
}

Now you can set your inspectable properties at the top of the Attributes Inspector in Interface Builder:

Attributes Inspector with inspectable property

Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • 1
    You shouldn’t have to use user-defined runtime attributes. Use `IB_DESIGNABLE` and `IBInspectable` to expose attributes with proper UI. More info [here](https://developer.apple.com/library/prerelease/ios/recipes/xcode_help-IB_objects_media/CreatingaLiveViewofaCustomObject.html). – Zev Eisenberg Jun 26 '14 at 22:05
  • I wasn't thinking to use implicitly unwrapped optionals. I'll try this when I get home. – jchitel Jun 26 '14 at 22:29
  • @ZevEisenberg is right -- see my additional comments – Nate Cook Jun 27 '14 at 02:24
  • 1
    So yea, IBDesignable and IBInspectable are awesome. Not only can you edit class properties as shown above, you can actually pre-render your custom views (custom drawing included) in IB without having to run your app. See [this](http://www.weheartswift.com/make-awesome-ui-components-ios-8-using-swift-xcode-6/). – jchitel Jun 29 '14 at 00:06
  • It can be a custom enum? – Daniel Gomez Rico Dec 01 '14 at 16:20
  • As a comment on @DanielG.R. , I wanted also to have a custom enum but it is not working so the simplest solution I found is to have the var defined as String! and then have an enum with raw values String and in the viewDidAppear to check if the var is not among the enum. If it is not I show an alert. Obviously these errors should not happen. – Čikić Nenad Jan 08 '15 at 05:12
  • 1
    hi nate, your answers really help me a lot, theres still one problem left though. When i call the runtime attribute with just didst, it still get the nil value, after that then comes the value, so my Xcode keep getting null pointer before the real value comes. – Bhimbim Oct 01 '15 at 23:32
  • when i was using this i was using the Number type, in your code though i thought it would map to a swift `Int` but it will crash on that, you have to use `NSNumber` instead – Fonix May 18 '16 at 04:49
  • 3
    apparently this doesn't work any more in Xcode 10/Swift 4.2: `Failed to set (url) user defined inspected property on (MyApp.ViewController): [ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key url.` – taber Feb 03 '19 at 22:28
  • 2
    are we just gonna shut up about how buggy IBDesignables & IBInspectable are? not saying it's a bad idea, just asking – noripcord Jul 18 '19 at 20:21
  • I'm going crazy because interface builder lags constantly even with "Automatically Refresh Designables" off. I can tell it's caused by IBInspectables because turning them off or just not being on the Inspectable tab allows me to use interface builder without lag. What am I doing wrong? I Just want to use IBDesignables without intense lag. – James Joshua Street Mar 31 '21 at 10:08
  • For UDRA you need to add @objc to your property definition now (if it's not IBInspectable). It used to be that Xcode did this implicitly, but that's no longer the case – joel.d Jun 30 '22 at 18:32