I have found a way to use designables and inspectables with Cocoa Touch frameworks. The instructions below are for an Objective-C project and Xcode 8 (I didn't test on older versions), and should be identical if Swift code is involved.
Since designables are not discovered by Interface Builder in frameworks, it is useless to mark classes as IB_DESIGNABLE
in the framework headers. Interface Builder will only discover designable classes when compiling project source files. The idea is therefore to supply this information as a framework companion source file, which clients can then compile with their project.
I discovered that you do not have to subclass to mark a framework class as designable in a project. You can namely simply annotate each class which must be designable through a category declared in the companion .m
source file, e.g.:
IB_DESIGNABLE
@interface MyCustomView (Designable)
@end
In fact, the code does not even have to compile, you could wrap it within an enclosing #if 0 ... #endif
and it would still work. All that is needed is that the class is somehow associated with the IB_DESIGNABLE
attribute.
With this information in mind, here is how to make designables work with Cocoa Touch frameworks:
If you are a framework vendor:
- If needed, have the component which must be designable implement
-prepareForInterfaceBuilder
- Add a Folder reference (blue folder) to your framework target, with the companion
.m
file in it. A possible naming convention would be to name the folder Designables
and the file within it
MyFrameworkNameDesignables.m
, but you can choose whatever you like most.
- In the
.m
file, create a category like the one above for each view which must be designable. The file itself must be compilable by client projects, which means you either need to make the necessary imports (e.g. your framework global public header #import <MyFramework/MyFramework.h>
) or use the #if 0 ... #endif
trick above
By enclosing the file within a blue folder, we ensure the folder is copied as is in the final .framework
product, without the companion source file being compiled. Moreover, as the folder is part of the framework bundle, it is available for all clients of the framework, whether they integrate it directly or using Carthage.
If you have a demo project using the framework as target dependency, and if your framework depends on other frameworks, you will run into dlopen
issues when trying to render designable views in the demo project. This is because IB_DESIGNABLE
attributes are discovered in the framework target (since the Designables
folder has been added to it), which Xcode pre-builds in the Build/Intermediates/IBDesignables
derived data folder corresponding to your project. If you look at the content of this folder, framework dependencies are missing, leading to the dlopen
issues.
To fix rendering in your demo, simply add a Copy Files phase to the framework target, add each required framework dependency to the file list, and set Products directory as destination. Now, when Xcode builds your demo for rendering, it will include the dependencies as well.
If you are the user of a framework with designable support:
- Add the framework (and all its framework dependencies, if any) as embedded binary to your target
- Retrieve the companion source file from the framework bundle and copy it into your project, adding it to your target. Adding the file located within the framework or using a symbolic link sadly does not work, as Xcode does not seem to look within frameworks at all
- Add an instance of the designable view class (
MyCustomView
in our example above) to a storyboard. Interface Builder should build the project and render the view
This solution is not perfect since you still have to manually copy a supplied source file, which could change between framework versions. But it works pretty well, though, and provides everything required within the framework bundle itself.