您的位置:首页 > 产品设计 > UI/UE

Pushing the Limits of User-Defined Runtime Attributes in Interface Builder

2014-06-25 14:06 615 查看
User-defined runtimes attributes in Xcode’s Interface Builder are a great way to keep view controller and view code clean, while obeying Separation of Concerns.They allow you to configure properties on the view or view controller that you are unable to configure
from within Interface Builder’s Attributes Inspector or Size Inspector.

But sometimes you may want to setup from Interface Builder a scenario which you can’t do from simple KVC-compliant property manipulation.

One example I ran into recently was trying to set the content insets on a collection view in a normal view controller in an iPhone-specific storyboard. In Xcode 5.1 you are unable to set the content insets of a collection view (I’m not sure in which Xcode
version this was first missing – I’m sure you used to be able to do it).So with this feature missinh, it would be nice to be able to do the same thing using user-defined runtime attributes.

Because contentInset is of
UIEdgeInsets
type, this isn’t possible with any of the types defined in Interface Builder under user-defined runtime attributes. But it can be done!

Another example I will show below is how to set the borderColor property of a
CALayer
, even though the runtime attributes don’t support
CGColorRef
type. All will be explained!

Runtime Attributes Explained

First a bit of background: user-defined runtime attributes can be set from the
Identity Inspector tab in Interface Builder utilities, as shown below:



They are defined as a set of key-value pairs. The key is actually a key path, which is a powerful, especially in Interface Builder where you don’t have direct access to the underlying
CALayer
of a
UIView
(more on this below).

The limitation is that the object’s class must be KVC-compliant for the key (or key path) defined in the set of runtime attributes. To be
KVC-compliant for a key
foo
simply means instances of the class must respond to the selector
setFoo:
.

Really, being KVC-compliant for a property means the class should implement the methods required for
valueForKey:
and
setValue:forKey:
to work for that property. For user-defined runtime attributes, that means implementing the setter for the property (key).

The following property types available for runtime attributes in Interface Builder:

Boolean – translates to a
BOOL
property
Number – can translate to any numeric scalar property or a property of type
NSNumber *

String – translates to a property of type
NSString *

Localized String – the value here is a key to look up in the
strings
file for the current locale.
Point – translates to a
CGPoint
property
Size – translates to a
CGSize
property
Rect – translates to a
CGRect
property
Range – translates to an
NSRange
property
Color – translates to a property of type
UIColor *

Nil – this spectial type doesn’t allow you to set a value, it is just a way of specifying that the value should be set to
nil


The types Point and Size can be used interchangeably, because they both map to the same type of C structure (
struct {CGFloat v1; CGFloat v2;}
).

Some Examples

Configuring a CALayer

A great example of using runtime attributes is to configure a
UIView
’s underlying
CALayer
. For example, we can set the layer’s border width and corner radius as follows:



Unfortunately, Interface Builder doesn’t allow us to set the color of a CALayer, because the
Color type doesn’t translate to properties of
CGColorRef *
type (workaround discussed below).

Configuring Custom Controls

Another example is if you are using a custom
UIControl
object such as a range slider. A range slider is similar to the built-in slider, but has two thumbs, or knobs: one to specify the minimum value and one to specify the maximum value.This
kind of control would be useful for setting a minimum price and maximum price in a search query. There are
various
implementations
available in the community.

Using user-defined runtime attributes, you can configure such a control right from within Interface Builder. Taking the example of
NMRangeSlider, you could configure the minimum and maximum values for the slider as follows:



The main benefit of Interface Builder in my opinion is that it keeps all the UI configuration logic in one place outside of the view controller. By configuring your controls in Interface Builder, this is yet more code that can be removed from your view controller.
After all, you would configure your
UIButton
and
UISlider
controls in Interface Builder, so why not configure your custom controls too?

The Limitations

As discussed above, the class must be KVC-compliant for each key you specify in the runtime attributes. If you specify a key path, then the object returned for each key must be KVC-compliant for the following key in the path.

For example, the list of types shown above means that we can’t set the content inset on a
UICollectionView
because there is no
UIEdgeInset
type (and no type that uses the exact same C structure type).

We also already discussed the fact that we can’t set the border color of a
CALayer
. Wouldn’t it be nice if anything you can think of, you could configure in Interface Builder without adding logic to your view or view controller?

The Workaround

Define a category on the object you wish to configure. Comply to KVC in the category and put the configuration logic in the setter method. You don’t even need to
#import
the category header anywhere – just by having it in the project, the runtime will call the setter method.

Define a Category

For example, by defining a category on
UICollectionView
, we can make the class KVC-compliant for any key we choose. To set the content inset, we can define the following category implementation:

1
2
3
4
5
6
7
8

@implementation UICollectionView (ContentInset)

- (void)setContentInsetFromString:(NSString *)string
{
self.contentInset = UIEdgeInsetsFromString(string);
}

@end

There’s no need to define anything in the category header.

This particular example is especailly simple because of the
UIEdgeInsetsFromString
mathod, which could it seems have been created specifically for this purpose!

Configure the Interface

Now we can set the user-defined runtime attributes in Interface Builder like so:



The Key Path must match the name of the setter method in the category, but without the
set prefix and with lowercase first letter.

Now you’ve configured your interface in Interface Builder – the correct place – and you can avoid having clutter in your code!

An added bonus of doing things in Interface Builder is that if you use separate nibs or storyboards for iPad/iPhone then you can configure your view differently for each device without cluttering your code checking the user interface idiom.

Another Example

Now we can define the border color of a
CALayer
by simply mapping it from a
UIColor
.

Define a Category

1
2
3
4
5
6
7
8

@implementation CALayer (Additions)

- (void)setBorderColorFromUIColor:(UIColor *)color
{
self.borderColor = color.CGColor;
}

@end

Configure the Interface



Considerations

I think this is the right way to configure a user interface. Given that we have Interface Builder, we should use it to its full potential. But… there’s always a “but” isn’t there?

Many people wouldn’t think to look in Interface Builder at the user-defined runtime attributes, so they could be left very puzzled if they come to maintain your code at a later date. They could be left with a seemingly random border to their view, unable
to figure out why it’s there.

So like anything of this nature — and by that I mean a more-advanced use of a programming language or development evironment — we should use it with caution. Use it only if it is necessary and simplifies logic elsewhere in your code.

Setting up a view in a view controller isn’t necessarily messy. The most valid reason for using user-defined runtime attributes is to configure a view differently whether the Interface Builder file is for iPhone or iPad. Because you already have a separate
nib or storyboard file specifically for each device, that should be the place that you do any device-specific configuration.

I hope you enjoyed reading this article. If so, please
follow me on twitter for more of the same.

Posted by Sam Dods Apr 8th, 2014
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐