Ever thought of defining a custom Cocoa class (inheriting from NSView or a descendant), associate it with a xib to be able to define its UI with Xcode designer, and then put everything into a framework that you could reuse internally or publish/sell it to others?
It should be easy, right? But if you would try it for the first time, you could easily find out that there a few major hassles to pass in order to get a proper solution.
(Disclaimer: I know there are many developers who don’t like to use Xcode for designing UI, but I personally think that defining such UI declaratively using XML – do it manually if you don’t like the designer – is better than writing spaghetti code for it.)
Now back to our goal, we can reach it in a few technical steps:
- Create a Cocoa framework target in the Xcode project, to host our custom view(s) (and link its binaries to the app).
- Add a View file (xib) to the new target folder, and define the UI for the control.
- Add a Cocoa class file to the same location, using the same filename as the .xib, inheriting from NSView or a descendant class. Make sure it’s marked as public, as it would be eventually accessed from outside the framework.
- Go back to the View, and select the newly added type as the File’s owner‘s class.
- Use Xcode‘s assistant editor to show both the view and the class file on screen, and control-drag to define outlets for the important UI elements within the view’s class.
- In the view class, redefine initializers to load the view from the nib (binary resource generated from the xib file), and add it as a subview (!) of the custom view.
- In the app, use the custom view from the framework normally.
Note that I’ve not included too many technical details in the list of steps above because I’ve posted a fully working project on GitHub, defining a CustomView within a CustomViews.framework and you should be able to gather everything from there.
But still, I think we need to answer ourselves a few conceptual questions before accepting this as a technical solution. Specifically:
- Why does Xcode allow/suggest creating a xib when we define a custom class inheriting from NSViewController, but not from NSView?
- I think it’s just because Xcode doesn’t want to go through the subview approach proposed above, while a controller could easily work together with a (default) view.
- Note that even if you’d create a NSViewController with an associated xib for the UI through Xcode, it would work only within the app’s module – from a framework it would give an error at loading because the xib won’t be found within the app’s main (“null”) bundle!
- Is it OK to actually have the view loaded from xib as a subview of the custom view?
- It doesn’t feel like an ideal solution but technically it works and I didn’t find any caveats yet. And (in my opinion) this is better than creating all the UI internals from code alone.
Finally, a small heads-up: the solution described above could partially (or entirely) apply to developing CocoaTouch views too, but it would probably require a few more steps. Still, for iOS development you can usually find more articles on the Web already, so I haven’t prepared anything myself on that side (yet). But, to address this at least with a link – if you’re on that thread, you can check this post instead.
Update: To support @IBDesignable and as well as a possible performance improvement, you should also ensure that your NSView-based class sets canDrawSubviewsIntoLayer to true. (I’ve modified the GitHub repo’ source code accordingly.)