Fixing Xcode bugs that make SwiftUI previews fail in apps modularized with SwiftPM and that are using CoreData
My SwiftUI previews didn’t work properly since the day I had set up the project for the Open Focus Timer in Xcode using Point-Free’s modularization approach — with the CoreData checkbox enabled to get a good starting point for my model layer. This was quite annoying, after all getting faster builds and therefore more reliable SwiftUI previews was one of the main reasons I had opted to modularize my app into small chunks in the first place.
So in one of my streams (this is an open-source app I am developing fully in the open while streaming live on Twitch) I decided to tackle this problem and fix the SwiftUI preview error once and for all. And I failed:
Thanks to some help from the great Swift community on Twitter, I could figure out the root cause of the issue:
SwiftUI previews get into trouble when CoreData models are referenced in them.
But while I thought that it’s just a path issue that can be fixed with a simple workaround, it was not as simple as that. Yes, there is a path issue involved, but while solving the previews, I came across multiple levels of failure. And I learned how to debug SwiftUI previews along the way. Let me share my learnings…
First things first. Using Point-Free’s modularization approach means you’ll have a
Package.swift file to manage manually. For each module, you’ll add a
testTarget and a
library entry and for each target, you’ll need to specify the dependencies. Xcode does not help here in any way other than recognizing the changes you make in that file. With many packages, the manifest file can grow significantly, and there’s currently no help I’m aware of to make this easier. This is what my manifest looks like right now:
The problem with managing this file manually isn’t just the manual work. Xcode seems to behave inconsistently regarding the dependencies: When you do a normal build targeting the Simulator for example, a dependency of a dependency seems to get automatically linked to your target. So if my
TimerFeature is importing
Utility for example, but it’s not listed as a dependency under the
TimerFeature target, Xcode might still be able to compile without errors if another dependency, eg
Model also depends on
Utility so Xcode can indirectly access
Utility inside of
TimerFeature is listing
Model as its dependency.
While this sounds very useful, it can become quite frustrating because SwiftUI previews work differently. For them, as far as I can tell, this transitive kind of implicit imports don’t work. The same seems to be true for running tests as well (at least sometimes). In other words: It’s important to always double-check the
dependencies for each target and not to forget to add every
import you make in a target to the related target in your
Package.swift manifest file.
Maybe, someone will write a tool to help make this easier in the future. 🤞
Another issue I had come across was that even when my builds succeeded, Xcode would (after showing me the “Build succeeded” dialog) show an error in the editor within
PreviewProvider stating it can’t find
While not necessarily a blocker, this made me feel the SwiftUI previews might also fail due to the generated code. To fix this, I opted for asking Xcode to generate the code files once and adding them to my packages explicitly. This can be done by opening the
.xcdatamodel file, then clicking
Editor and choosing
Create NSManagedObject Subclass...:
Note that you will need to delete and re-create these generated files each time you make a change to the model (which you should rarely do anyways to prevent database migration problems). Additionally, select the model in Xcode and set
With this done, the editor no longer shows an error.
Here’s a learning for those (like me) wondering how to make use of errors like this after pressing the
Diagnostics button when SwiftUI previews fail:
How is this error message supposed to help us, it’s not very useful:
Message send failure for send previewInstances message to agent
MessageError: Connection interrupted
To get more details, read the small gray text below the title of the modal:
Use “Generate Report” to create information that can be sent to Apple to diagnose system problems.
This hint is quite misleading. It sounds like this step is only useful to help Apple analyze the problem. But we can use it, too! Just click the “Generate Report” button and select “Reveal in Finder” in the dropdown. Then Xcode will generate a report and open the Finder app with the generated folder highlighted like this:
Viewing the contents of the highlighted folder will reveal many files that hold different kinds of details about the SwiftUI preview build. The most useful file for debugging lies inside the folder
CrashLogs where you can find one or multiple
.ips files that we can easily open in Xcode via a double-click:
The contents of this file look much more like the error outputs we get in Xcode’s console when builds fail, including the very reason the build failed and even a stack of calls that happened at the time of failure. It states:
Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[FocusTimer running]: unrecognized selector sent to instance 0x12886cac0′
Now we have a place we can start debugging and we know that for some reason SwiftUI previews could not access the
running property of our
FocusTimer model. This was key to making the connection to CoreData, otherwise I would have to have a wild guess why the previews were failing.
I think Xcode should just show this stack trace in the Diagnostics screen right away, this would have helped me save some time. Maybe in Xcode 14? 🤞
After playing around a little bit with different things, I found the root cause for the
unrecognized selector issue: It was related to how I created my mocked
FocusTimer object for use within the
By the way: Yes, I am putting all code related to SwiftUI previews (including the
#if DEBUGdirectives. This ensures I never accidentally call into code that I only wrote for SwiftUI previews in my production code.
I was thoughtlessly calling the
.init method on my
FocusTimerwhich is a subclass of
NSManagedObject as I thought that’s the easiest way to initialize an empty
FocusTimer. But there is no
init method on
NSManagedObject itself is a subclass of
NSObject and the init() is defined on that level. This does not create a proper CoreData model though, instead we need to call the init(context:) method of
Thankfully, when creating a new Xcode project and lighting the
CoreData checkbox, Xcode creates a
PersistenceController file with an init method that accepts an
inMemory: Bool parameter:
This is important because we don’t want to create actual databases in our previews (this could cause another error making our previews fail), instead we just want to use an in-memory database which never gets actually persisted (despite the name
Note that I had to replace the first line creating the
container with the following 3 lines to make it load the CoreData model from the correct path when extracting the CoreData model code into a separate SwiftPM module:
Next, I added this
mocked property to the
Now, I adjusted the
FocusTimer mock by calling into the correct init method:
This fixed the
unrecognized selector error in SwiftUI previews! 🎉
But it was not over yet, there was one more very weird Xcode bug to fix …
Lastly, with all the previous steps applied, I came across this error stating:
Fatal error: unable to find bundle named OpenFocusTimer_Model
Thankfully, here the aforementioned pointer of a kind developer in the Swift community on Twitter helped, which pointed me to a thread with this answer on StackOverflow.
It’s basically saying that there’s currently a bug in Xcode (or SwiftPM?) which makes
Bundle.module point to the wrong path in SwiftUI previews. To fix it, they are suggesting to add a
Bundle extension with a custom search. Here’s the full code slightly adjusted to fit my coding & commenting style:
When copy and pasting this code, make sure to adjust the
targetNamevariables to your package & target names accordingly.
Note that I wrapped the workaround into an
#if DEBUG To ensure my production code does not accidentally use this path search and instead relies on the official
Bundle.module. Also, I removed the
fatalError from the workaround code found on StackOverflow, so in case it can’t find a Bundle in the custom search paths it doesn’t fail but instead I
return Bundle.module as a fallback. This is supposed to make the code more resilient and continue to work even when this bug gets fixed in a future Xcode release but the custom search paths may no longer work.
Now, the last change I had to make in the
PersistenceController was to replace the call to
Bundle.module with a call to the new
And finally, my SwiftUI previews started working again!
Want to Connect?You can also find me on 👾 Twitch, on 🎬 YouTube and on 🐦 Twitter.