Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IBDesignable from External Framework?

I'd like to create some custom views that use the @IBDesignable and @IBInspectable tags. I have added these to my Framework and then linked my Framework to my test application. But the Designables never show up in the StoryBoard.

How can I use @IBDesignable and @IBInspectable to create custom views from an external framework?

Can you use @IBDesignable and @IBInspectable in an application from a non-embedded Framework?

Thank you.

like image 716
Sakiboy Avatar asked Apr 29 '15 03:04

Sakiboy


2 Answers

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 .msource 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:

  1. If needed, have the component which must be designable implement -prepareForInterfaceBuilder
  2. 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.
  3. 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.

like image 147
defagos Avatar answered Oct 14 '22 11:10

defagos


I have a workaround. With this workaround, you don't need to add the framework as target. So it works with Carthage.

@IBDesignable
class MyCustomView: CustomView {

    @IBInspectable override var bgColor: NSColor {
        get {
            return super.bgColor
        }
        set {
            super.bgColor = newValue
        }
    }
}

Create a sub class (MyCustomView) of the custom class (CustomView) in the destination project (not in the framework project), and mark the sub class as @IBDesignable. Use sub class in your app. This way makes @IBDesignable work.

Inside the sub class, override those @IBInspectable properties(bgColor), this way makes @IBInspectable work.

You might encounter this issue: Loading code from a framework for a custom control in IBDesignable Following this guide to solve it: http://www.dribin.org/dave/blog/archives/2009/11/15/rpath/

And please make both the custom class and its inspectable properties public otherwise this method doesn't compile.

Please leave comments if you cannot get it work.

like image 41
Tyler Liu Avatar answered Oct 14 '22 09:10

Tyler Liu