How to reuse complex xib-designed views in storyboards using modern swift generics, property wrappers and dynamic member lookup?
To answer this rather long question (take a deep breath and try not to say it three times in a row at home, we are professionals), you’ll need at least Xcode 11 and a project using Swift 5.1.
I ran into this problem many times, and the last one was when I wanted to display and reuse a message view for our new B2B application ExtraDoc, here at Extrabat. I have read many things about how reusing xib files in storyboards and unfortunately none completely satisfied me, especially because none really used the whole power of the latest swift release.
We will develop a simple project, containing a complex message view designed in a xib file. Then, we’ll use it and preview it in a storyboard. Finally, we’ll try to access its properties using an outlet in an elegant way.
Project creation
First let’s create a single view app called ReusableXibViews using Swift and Storyboards.
Xib creation
Well, let’s dive in with the creation and the design of our message view. Create and add the MessageView.xib file to your project.
Choose the Freeform size in the simulated metrics.
Then, design your view the way you want.
The next classical step consists of creating the associated UIView
class, let’s call it MessageView.
Note: It is important to name the xib file with the name of the class. Later we’ll use a helper function using this as a convention.
Once done, just assign the class in the Identity inspector, and connect the outlets in the Connection inspector.
Well, well, well… Nothing really new, so let the fun begin…
Storyboard inclusion
So, we’d like to have our view in our storyboard, first step : load the view from the xib. To do it, we’ll add a little extension to UIView
to load the view from the xib named after the class.
Add the file UIView+Nib.swift.
Note: We’re talking about xib files and everywhere we use in code the term nib. That’s just an historical convention. A long time ago, Interface Builder files were stored in nib files, then Apple changed its format for XML, and gave a new name: xib. Thank you for reading this little historical break…
The really important part is the next step: we will create a generic class designed to wrap the view, whatever its type. We need that for the storyboard to preview our view, thanks to the prepareForInterfaceBuilder
method. Here we simply load the content view from the xib file and add it as a subview of the wrapper.
As our old friend Interface Builder does not really like generics (probably too fancy for him and his old buddy Objective-C) we have to define a class to specialize generics and to be used in storyboards.
Go back to the MessageView.swift and add this little class
definition:
So now it’s easy to figure out what we’ll do: go to your storyboard, add a plain UIView
and assign our brand new MessageViewWrapper
custom class.
And voilà!
Well, at least it should… Xcode being a bit picky with @IBDesignable
views it may:
- Take some time to appear
- Just do not appear… 🤷♂️
To check if there’s been a failure, check the designable state of the view in the Identity inspector.
If you have this, just wait…
And if you have a “failed” message… Well… Try cleaning the project, closing Xcode and the Simulator, it may be better after… You can try to turn your computer off and on again… Or run in underwear around a cemetery during a full moon night… (Please don’t! Try and fill a feedback to Apple instead it may be more efficient… perhaps…)
Anyways… Congratulations! You’ve succeeded to insert a custom xib view inside a storyboard… Great, but now, what if you want an outlet out of it?
To outlets and beyond…
So let’s try to do it as we would do with any view…
Damnit! Interface Builder has its revenge to prevent us from using Swift generics… (I’m sure you can hear its evil laugh!) Indeed, we can’t make a @objc
generics class, and @IBOutlet
must be…
But we are smarter than that, aren’t we?
Ok IB, you want an Objective-C class, let’s give you one!
Yay! It works…
So let’s use it:
Oh wait…
We’ll have to perform all those casts all the time and we will loose all the beauty we’ve been working for… Happily Swift 5.1 adds this cool feature: Property Wrappers… And it will make our day.
So let’s define a Property Wrapper to do all the casting for us!
So now we can use it this way:
You can access the casted value of your view with _messageView.unwrapped
and write things like this:
Thats great!!! But… Can’t we go further?
Dynamic member lookup to the rescue
Thanks to this little addition to Swift 4.2, we will be able to call the properties directly instead of using the unwrapped
property.
Just update the NibWrapped.swift file with this:
So now it is (almost) completely transparent and you can use directly the _messageView
as if it were a MessageView
class:
Note: I said almost transparent, because we still need to use the property wrapper
_messageView
and not the proper outletmessageView
but I still think it’s a fair compromise…
Conclusion
So thanks to all this, when you want a new reusable and previewable xib view, just:
- Add a class to specialize the generics and set it as the a view’s custom class in a storyboard
- Add a simple
UIView
outlet with the property wrapper indicating the target class - And that’s it!
Well, I hope you’ve liked the elegant way we can use modern Swift features along with our good old Interface Builder. (Who’s laughing now?!?)
Note: You can find this sample project on github: https://github.com/letatas/ReusableXibViews