Embedding custom Cocoa macOS views with xibs into frameworks

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:

  1. Create a Cocoa framework target in the xCode project, to host our custom view(s) (and link its binaries to the app).
  2. Add a View file (xib) to the new target folder, and define the UI for the control.
  3. 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.
  4. Go back to the View, and select the newly added type as the File’s owner‘s class.
  5. 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.
  6. 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.
  7. 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:

  1. 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!
  2. 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.)

Advertisements

About Sorin Dolha

My passion is software development, but I also like physics.
This entry was posted in macOS and tagged , , , , , . Bookmark the permalink.

Add a reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s