iOS Tip: Add member into Object dynamically with Extensions
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.