Dude, where’s my view? Unknown class in iOS 12
When XCFramework was announced and “Build Libraries for Distribution” became available to developers, everything seemed too perfect to be real. No more “workarounds” with fat frameworks, allowing just one package to contain many architectures, simplifying builds. It felt magical! And best of all, you could use it even when supporting old versions of iOS. Isn’t it wonderful?
It is, if you don’t have to deal with storyboards and custom views. 🥲
Dude, where’s my view?
That’s exactly what you read. Your view, beautifully shown and totally functional, becomes simply invisible to storyboard/XIB in iOS 12.
But hold a sec. It’s not that simple, and I’ll explain what is happening here.
Before I start, let’s take a look at a little recipe to reproduce this error.
- Create a framework, add a struct, and change the Build Libraries for Distribution flag to YES.
- Import that framework into your app’s project.
- Create a custom view (using XIB or view code, it’s up to you) and add it to a XIB or storyboard.
- In your custom view class, add the struct that you created in the previous framework.
- Run your code on a device or simulator running iOS 12 or earlier.
It seems like a very specific scenario we have here, one of those that doesn’t show up every day, right? But believe me, it’s more common than you might think!
For example, if you have a framework where your models (usually structs) are placed, and one of those structs is used in a view to hold some value or just as a transport object, you may face this problem!
If you notice that a view is not shown and this problem occurs only in iOS 12, check your app’s logs. Do you see anything like the message below?
I know. You’ll read the message and instantly think: “DAMN YOU, INTERFACE BUILDER!” But hold on a sec, bud…
Interface Builder: the troublemaker or just another victim?
Interface Builder is as innocent as you are. Let’s understand why.
Interface Builder says that the class is unknown to it. But the class is there. You can use it. You can access it. So why is it unknown?
Why don’t we ask the objc runtime and see what it says?
Well, it’s true. It’s unknown! But hold a sec. If I ask it to get the class, using the format “Module.ClassName,” the objc runtime finds it. And after that, if I try to get the class once more, it finds the damn class! How is that possible?
Is it happening in iOS 13 too? Let’s take a look.
Nope, in iOS 13, the objc runtime finds the class on the first try. No workarounds needed. No problems.
What the hell is happening here?
The logic explanation
When you build a framework using Build Libraries for Distribution, you are enabling the ABI Stability feature from Swift. This feature generates a swiftinterface file, literally a text file, holding a description of your public classes, so future versions of Swift and its compiler can understand the framework contents without needing a new compilation. That’s fantastic, but it comes with a cost. The objc runtime from previous versions may not be as prepared for this new technology.
As this tech showed up with iOS 13 and Xcode 11, it’s natural that previous versions couldn’t use all available resources. However, when this happens, Xcode itself shows an alert informing you that this resource is only available for iOS 13 or later, but that is not the case here.
There is not much to do here because it is an OS limitation, but we have some “workarounds” in this case.
The “workaround” solutions
We have three possible “workarounds” here: we may change our struct to a class, force objc runtime to find our class “on our own,” or we may disable Build Libraries for Distribution.
Shifting struct to class
The first solution is self-explanatory, without any mystery: we’ll shift our struct to a class.
Using this solution, your code, which looks like this:
public struct MyStruct { … }
will turn into this:
public class MyStruct { … }
Horrible, isn’t it? But prepare yourself, our next solution is even worse! 😈
Forcing objc runtime to find your class “on your own”
There are basically two ways of doing this. You may use objc_getClass
, passing a string with your module’s name (target) and your custom view’s name (if your custom view belongs to your app’s target, the module name will be your app name, like MyApp.MyCustomView), separated by a dot.
if #available(iOS 13, *) { } else {
objc_getClass(“TesteFramework2.MyCustomViewClass”)
}
If you don’t want to work with the objc runtime, you may easily use NSClassFromString
, passing the name of the class you want to load in the same way as the previous example.
if #available(iOS 13, *) { } else {
NSClassFromString(“TesteFramework2.MyCustomViewClass”)
}
These two methods assume that you know exactly which classes you need to force load at runtime, which may be impractical during development but may be very useful to fix a specific bug.
Disabling Build Libraries for Distribution
Another possible solution is to stop using Build Libraries for Distribution, simply setting this flag to NO in Build Settings.
A warning here: disabling Build Libraries for Distribution means you will not be able to use XCFrameworks and the ABI Stability. In other words, you will lose two really useful features if you choose this path.
Think twice before taking this path, and take it only as a last resort.
BONUS: no more iOS 12 supporting (???)
In a perfect world, where flowers bloom in the fields, people sing with the wind through their hair, and unicorns fly leaving a rainbow path in the sky, you may simply drop iOS 12 support from your app 🌈🦄.
But we know that reality is pretty different. Whatever can go wrong, will go wrong! 👊💣
Dropping iOS 12 support means that you’ll no longer support two important devices, which have a significant user base, mainly in emerging economies: iPhone 5S and iPhone 6.
Is your user base for those devices so small that it’s worth dropping support? Then this is a good choice.
Just a little tip: Apple still releases some security updates for iOS 12 (the last one was released on June 14). If even Apple is still working on security fixes for this version, is it really a good idea to drop it?
Don’t you believe it? Check it out!
If you want to try and see this error with your own eyes, I uploaded a sample in a Github repo, so you can check it out TOTALLY LIVE!!!
https://github.com/AndreasLS/struct-ib-ios12-bug
Clone the repo (and download the iOS 12 simulator too) and test it. 😉