Reusable View Components from Nibs
Just found this today in a Slack channel. It seems we can actually reference and re-use Xib files by overriding awakeFromCoder
!
If we are loading from placeholder view, we create a real view and then we transfer some common properties from it and all it’s subviews, then we just return that instance in place of placeholder, otherwise we just return normal view (This method is implemented on NSObject so we can call super, but this still should be done with method swizzling instead of category smashing).
There’s a sample project on GitHub. Let’s tear it apart.
It works like this:
const int kNibReferencingTag = 616;
@implementation UIView (NibLoading)
// ...
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
{
if (self.tag == kNibReferencingTag) {
//! placeholder
UIView *realView = [[self class] loadInstanceFromNib];
realView.frame = self.frame;
realView.alpha = self.alpha;
realView.backgroundColor = self.backgroundColor;
realView.autoresizingMask = self.autoresizingMask;
realView.autoresizesSubviews = self.autoresizesSubviews;
for (UIView *view in self.subviews) {
[realView addSubview:view];
}
return realView;
}
return [super awakeAfterUsingCoder:aDecoder];
}
When the kNibReferencingTag
is set, the current instance (self
) is treated as a prototype. From that prototype we transfer common properties to the realView
which is properly loaded from a Nib. That means it doesn’t go the kNibReferencingTag
path but the usual path, deferring to super
.
The sample app contains SomeView
with its own Xib that should be reused.
In the app’s Xib, we have a scene that uses it as follows:
<scenes>
<!--View Controller-->
<scene sceneID="4">
<objects>
<viewController id="2" customClass="ViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="5">
<rect key="frame" x="0.0" y="20" width="768" height="1004"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view tag="616" contentMode="scaleToFill" id="VTu-Cz-9kE" customClass="SomeView">
<rect key="frame" x="113" y="216" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
<view tag="616" contentMode="scaleToFill" id="7te-Q6-moz" customClass="SomeView">
<rect key="frame" x="199" y="446" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
<view tag="616" contentMode="scaleToFill" id="7vx-xh-xRD" customClass="SomeView">
<rect key="frame" x="375" y="231" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="3" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
I know, Xib files aren’t the best to read. Here’s what it boils down to:
- Create a scene using a view controller of type
ViewController
. - In its main view place 3 subviews …
- of type
SomeView
, - all with the tag
616
, - and a few standard color properties – apparently all set to white. It doesn’t matter anyway since the
SomeView.xib
will dictate what it really looks like.
- of type
Unlike @IBDesignable
components, you won’t have any live preview in Interface Builder. Just empty placeholder boxes.