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 goal
, 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 Utility
inside 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 FocusTimer
:
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 Codegen
to Guide/None
.
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 PreviewProvider
:
By the best way: Sure, I’m placing all code associated to SwiftUI previews (together with the
PreviewProvider
) inside#if DEBUG
directives. 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 NSManagedObject
.
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 PersistenceController
).
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 PersistenceController
:
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
Fortunately, right here the aforementioned pointer of a kind developer within the Swift neighborhood on Twitter helped, which pointed me to a thread with this answer on StackOverflow.
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
packageName
andtargetName
variables 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 Bundle.swiftUIPreviewsCompatibleModule
:
And eventually, my SwiftUI previews began working once more!
Wish to Join?You may as well discover me on 👾 Twitch, on 🎬 YouTube and on 🐦 Twitter.