iOS Tip: Add member into Object dynamically with Extensions

Lee young-jun
3 min readNov 5, 2023

--

You might know that Extensions can define only computed properties using already defined members of the object.

extension UIView {
var borderLeftWidth: Float = 1
}

error: playground.playground:5:9: error: extensions must not contain stored properties
var borderLeftWidth: Float = 1
^

However I wanted to make new stored properties to draw a left line easily with Extension.

extension UIView {
@IBInspectable
public var borderLeftWidth : CGFloat{
get{
let borderLineView = ... //get from UIView
return borderLineView?.frame.width ?? 0.0;
}

set(value){
var borderLineView : UIView! = //get from UIView

if borderLineView == nil{
borderLineView = self._createNewBorderLineView();
}

borderLineView.backgroundColor = self.borderUIColor;
borderLineView.widthConstraints.first?.constant = value;
self.bringSubviewToFront(borderLineView);
}
}
}

How can I make variable to store the line view for each views?

objc_setAssociatedObject

There is a function to insert new member into the object. (Maybe it is saving it another space according to the descriptions)

Apple provides objc_setAssociatedObject.

func objc_setAssociatedObject(
_ object: Any,
_ key: UnsafeRawPointer,
_ value: Any?,
_ policy: objc_AssociationPolicy
)

First argument is an object to contain a new variable.

objc_setAssociatedObject(self,

Second is a key address to identify the new variable.

&UIView.BorderPropertyKeys.left,

/**
private struct BorderPropertyKeys{
static var left = "uiview.border.left";
}
*/

Third is a value to be stored as a new variable.

borderLineView,

Last is a property policy

.OBJC_ASSOCIATION_RETAIN

//like @property (nonatomic, retain) UIView *borderLIneView;

And I can add a new UIView into the view and store it dynamically like this.

objc_setAssociatedObject(self, 
&UIView.BorderPropertyKeys.left,
borderLineView,
.OBJC_ASSOCIATION_RETAIN);

So.. how can I get the stored value again??

objc_getAssociatedObject

Of source, there is also a function to get stored variable. Just replace ‘set’ with ‘get’. Right! It is objc_getAssociatedObject.

func objc_getAssociatedObject(
_ object: Any,
_ key: UnsafeRawPointer
) -> Any?

I won’t explain how to use this here. (You can already know by argument names 😄)

Property

With those functions I created getter and setter to manage a border line view.

get

get{
let borderLineView = objc_getAssociatedObject(self, &UIView.BorderPropertyKeys.left) as? UIView;
return borderLineView?.frame.width ?? 0.0;
}

set

set(value){
var borderLineView : UIView! = objc_getAssociatedObject(self, &UIView.BorderPropertyKeys.left) as? UIView;

if borderLineView == nil{
borderLineView = self._createNewBorderLineView();

borderLineView?.widthAnchor.constraint(equalToConstant: value).isActive = true;
borderLineView?.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
borderLineView?.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
borderLineView?.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true

self.setObjcProperty(&UIView.BorderPropertyKeys.left, value: borderLineView, policy: .OBJC_ASSOCIATION_RETAIN);
borderLineView = self.objcProperty(&UIView.BorderPropertyKeys.left) as? UIView;
}

borderLineView.backgroundColor = self.borderUIColor;
borderLineView.widthConstraints.first?.constant = value;
self.bringSubviewToFront(borderLineView);
}

Advanced

I don’t like such a function names, therefore I created the shortcuts of those methods.

objcProperty

/**
Wrapper for objc_getAssociatedObject();
- parameter key: key for associated object
*/
public func objcProperty(_ key: UnsafeRawPointer) -> Any?{
return objc_getAssociatedObject(self, key);
}

setObjcProperty

/**
Wrapper for objc_setAssociatedObject()
- parameter key: key for associated object
- parameter value: value for associated object
- parameter policy: Release policy for associated object
*/
public func setObjcProperty(_ key: UnsafeRawPointer, value: Any?, policy: objc_AssociationPolicy) -> Void{
objc_setAssociatedObject(self, key, value, policy);
}

Final

This is my full source of the property.

Source

@IBInspectable
public var borderLeftWidth : CGFloat{
get{
let borderLineView = self.objcProperty(&UIView.BorderPropertyKeys.left) as? UIView;
return borderLineView?.frame.width ?? 0.0;
}

set(value){
var borderLineView : UIView! = self.objcProperty(&UIView.BorderPropertyKeys.left) as? UIView;

if borderLineView == nil{
borderLineView = self._createNewBorderLineView();

borderLineView?.widthAnchor.constraint(equalToConstant: value).isActive = true;
borderLineView?.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
borderLineView?.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
borderLineView?.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true

self.setObjcProperty(&UIView.BorderPropertyKeys.left, value: borderLineView, policy: .OBJC_ASSOCIATION_RETAIN);
borderLineView = self.objcProperty(&UIView.BorderPropertyKeys.left) as? UIView;
}

borderLineView.backgroundColor = self.borderUIColor;
borderLineView.widthConstraints.first?.constant = value;
self.bringSubviewToFront(borderLineView);
}
}

Storyboard

And I could use it in the storyboard attributes inspector!

There is also objc_removeAssociatedObjects, however I have never used it yet.

You can see original source of the property at this link.

Please give me 👏 if this post is helpful to you.
There are more posts about iOS.

Please visit my Linked-In profile if you want to know more about me.

References

--

--

No responses yet