Fixing Xcode bugs that make SwiftUI previews fail in apps modularized with SwiftPM and which are utilizing CoreData
My SwiftUI previews didn’t work correctly because the day I had arrange the undertaking for the Open Focus Timer in Xcode utilizing Level-Free’s modularization approach — with the CoreData checkbox enabled to get a very good start line for my mannequin layer. This was fairly annoying, in any case getting quicker builds and subsequently extra dependable SwiftUI previews was one of many fundamental causes I had opted to modularize my app into small chunks within the first place.
So in one of my streams (that is an open-source app I’m growing totally within the open whereas streaming live on Twitch) I made a decision to deal with this drawback and repair the SwiftUI preview error as soon as and for all. And I failed:
Due to some assist from the nice Swift neighborhood on Twitter, I may determine the basis reason for the problem:
SwiftUI previews get into bother when CoreData fashions are referenced in them.
However whereas I assumed that it’s only a path problem that may be fastened with a easy workaround, it was not so simple as that. Sure, there’s a path problem concerned, however whereas fixing the previews, I got here throughout a number of ranges of failure. And I discovered how one can debug SwiftUI previews alongside the best way. Let me share my learnings…
First issues first. Utilizing Level-Free’s modularization method means you’ll have a
Bundle.swift file to handle manually. For every module, you’ll add a
testTarget and a
library entry and for every goal, you’ll have to specify the dependencies. Xcode doesn’t assist right here in any approach apart from recognizing the modifications you make in that file. With many packages, the manifest file can develop considerably, and there’s presently no assist I’m conscious of to make this simpler. That is what my manifest appears like proper now:
The issue with managing this file manually isn’t simply the guide work. Xcode appears to behave inconsistently relating to the dependencies: Once you do a traditional construct concentrating on the Simulator for instance, a dependency of a dependency appears to get mechanically linked to your goal. So if my
TimerFeature is importing
Utility for instance, but it surely’s not listed as a dependency below the
TimerFeature goal, Xcode may nonetheless be capable of compile with out errors if one other dependency, e.g.
Mannequin additionally is determined by
Utility so Xcode can not directly entry
TimerFeature as a result of
TimerFeature is itemizing
Mannequin as its dependency.
Whereas this sounds very helpful, it could develop into fairly irritating as a result of SwiftUI previews work otherwise. For them, so far as I can inform, this transitive form of implicit imports don’t work. The identical appears to be true for working checks as nicely (a minimum of generally). In different phrases: It’s necessary to all the time double-check the
dependencies for every goal and to not overlook so as to add each
import you make in a goal to the associated goal in your
Bundle.swift manifest file.
Possibly, somebody will write a software to assist make this simpler sooner or later. 🤞
One other problem I had come throughout was that even when my builds succeeded, Xcode would (after displaying me the “Construct succeeded” dialog) present an error within the editor inside
PreviewProvider stating it could’t discover
Whereas not essentially a blocker, this made me really feel the SwiftUI previews may additionally fail because of the generated code. To repair this, I opted for asking Xcode to generate the code information as soon as and including them to my packages explicitly. This may be carried out by opening the
.xcdatamodel file, then clicking
Editor and selecting
Create NSManagedObject Subclass...:
Be aware that you’ll want to delete and re-create these generated information every time you make a change to the mannequin (which you need to do hardly ever anyhow to stop database migration issues). Moreover, choose the mannequin in Xcode and set
With this carried out, the editor not exhibits an error.
Right here’s a studying for these (like me) questioning how one can make use of errors like this after urgent the
Diagnostics button when SwiftUI previews fail:
How is that this error message supposed to assist us, it’s not very helpful:
Message ship failure for ship previewInstances message to agent
MessageError: Connection interrupted
To get extra particulars, learn the small grey textual content under the title of the modal:
Use “Generate Report” to create info that may be despatched to Apple to diagnose system issues.
This trace is kind of deceptive. It seems like this step is barely helpful to assist Apple analyze the issue. However we are able to use it, too! Simply click on the “Generate Report” button and choose “Reveal in Finder” within 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 information that maintain totally different sorts of particulars concerning the SwiftUI preview construct. Probably the most helpful file for debugging lies contained in the folder
CrashLogs the place you will discover one or a number of
.ips information that we are able to simply open in Xcode through a double-click:
The contents of this file look rather more just like the error outputs we get in Xcode’s console when builds fail, together with the very cause the construct failed and even a stack of calls that occurred on the time of failure. It states:
Terminating app attributable to uncaught exception ‘NSInvalidArgumentException’, cause: ‘-[FocusTimer running]: unrecognized selector despatched to occasion 0x12886cac0’
Now we’ve a spot we are able to begin debugging and we all know that for some cause SwiftUI previews couldn’t entry the
working property of our
FocusTimer mannequin. This was key to creating the connection to CoreData, in any other case I must wild guess why the previews had been failing.
I feel Xcode ought to simply present this stack hint within the Diagnostics display instantly, this may have helped me save a while. Possibly in Xcode 14? 🤞
After taking part in round a bit of bit with various things, I discovered the basis trigger for the
unrecognized selector problem: It was associated to how I created my mocked
FocusTimer object to be used throughout the
By the best way: Sure, I’m placing all code associated to SwiftUI previews (together with the
#if DEBUGdirectives. This ensures I by no means by accident name into code that I solely wrote for SwiftUI previews in my manufacturing code.
I used to be thoughtlessly calling the
.init methodology on my
FocusTimer, which is a subclass of
NSManagedObject as I assumed that’s the best strategy to initialize an empty
FocusTimer. However there is no such thing as a
init methodology on
NSManagedObject, as a substitute
NSManagedObject itself is a subclass of
NSObject and the init() is outlined on that degree. This doesn’t create a correct CoreData mannequin although, as a substitute we have to name the init(context:) methodology of
Fortunately, when creating a brand new Xcode undertaking and enabling the
CoreData checkbox, Xcode creates a
PersistenceController file with an init methodology that accepts an
inMemory: Bool parameter:
That is necessary as a result of we don’t need to create precise databases in our previews (this might trigger one other error making our previews fail), as a substitute we simply need to use an in-memory database which by no means will get truly persevered (regardless of the title
Be aware that I needed to change the primary line creating the
container with the next 3 strains to make it load the CoreData mannequin from the proper path when extracting the CoreData mannequin code right into a separate SwiftPM module:
Subsequent, I added this
mocked property to the
Now, I adjusted the
FocusTimer mock by calling into the proper init methodology:
This fastened the
unrecognized selector error in SwiftUI previews! 🎉
Nevertheless it was not over but, there was yet another very bizarre Xcode bug to repair …
Lastly, with all of the earlier steps utilized, I got here throughout this error stating:
Deadly error: unable to seek out bundle named OpenFocusTimer_Model
It’s mainly saying that there’s presently a bug in Xcode (or SwiftPM?) which makes
Bundle.module level to the improper path in SwiftUI previews. To repair it, they’re suggesting so as to add a
Bundle extension with a customized search. Right here’s the total code barely adjusted to suit my coding & commenting type:
When copy and pasting this code, make certain to regulate the
targetNamevariables to your bundle & goal names accordingly.
Be aware that I wrapped the workaround into an
#if DEBUG to make sure my manufacturing code doesn’t by accident use this path search and as a substitute depends on the official
Bundle.module. Additionally, I eliminated the
fatalError from the workaround code discovered on StackOverflow, so in case it could’t discover a Bundle within the customized search paths it doesn’t fail however as a substitute I
return Bundle.module as a fallback. That is speculated to make the code extra resilient and proceed to work even when this bug will get fastened in a future Xcode launch however the customized search paths might not work.
Now, the final change I needed to make within the
PersistenceController was to exchange the decision to
Bundle.module with a name to the brand new
And eventually, my SwiftUI previews began working once more!