This guest post is written by John Sundell, host of the Swift by Sundell podcast.
‍
There are a lot of things to like about SwiftUI. Its declarative API makes it possible to build many common types of iOS views with very little code, its powerful state management system ensures that our UIs stay in complete sync with their underlying state and data, and Xcode’s new Previews feature lets us iterate on our views faster than ever before.
However, for all of its advantages, SwiftUI still has a long way to go when it comes to its overall compatibility with other tools. Even many of Apple’s own frameworks have not yet been updated to offer native SwiftUI integrations, and it’s very common to have to do at least some amount of work to make third-party tools and libraries work well in the declarative world of SwiftUI.
Thankfully, Instabug already works incredibly well with SwiftUI-based apps. Both network and UI events are still automatically logged, just like they are when using UIKit, and users and testers can keep invoking the Instabug feedback UI just like before.
That being said, there are a few simple things that we can do to make Instabug even easier to use within the context of SwiftUI, which is exactly what we’ll take a look at in this article.
‍
Starting Instabug when using the new SwiftUI app lifecycle
When using the new SwiftUI-based app lifecycle introduced in iOS 14, it might not initially be obvious where SDKs like Instabug should ideally be initialized. After all, there’s no more didFinishLaunchingWithOptions app delegate method to implement, so where can we put logic that we want to run as soon as our app has launched?
Perhaps the simplest approach is to give our main app struct an initializer that performs all of our on-launch setup work. In this case, we’ll call Instabug’s
start method directly within that initializer, which will ensure that Instabug will be up and running before any of our UI has been constructed:
‍
‍
‍
While the above works perfectly fine, let’s also say that we’d like to utilize some of Instabug’s many customization APIs to tailor it to fit our needs. At that point, we probably don’t want to keep writing all of our setup code directly within our app struct’s initializer, as doing so could quickly get quite messy — so let’s instead create a dedicated InstabugConfigurator type that we can then call from our app implementation.
Since we’re looking to configure Instabug globally for our entire app, let’s make our new InstabugConfigurator completely static, and by implementing it as a case-less enum, we can even prevent it from being accidentally initialized:
‍
‍
Note how we’re wrapping our configureIfNeeded implementation within a #if !DEBUG compiler directive. That’s to prevent this code from running during development, since we likely don’t want to initialize Instabug when we’re testing or debugging our code in either the simulator or on a device. We’re also preventing our configuration code from ever running twice, by setting a static hasBeenConfigured property to true when our configuration method was called for the first time.
With the above in place, we can now simply call our new InstabugConfigurator from within our App struct’s initializer, which still ensures that Instabug will be fully configured right when our app is launched, while also letting our App implementation remain focused on configuring our app’s scenes and their main UI:
‍
‍
Now that we’ve finished configuring Instabug within our SwiftUI-based app, let’s also take a look at how we could make it really simple to send Instabug UI events directly from within a SwiftUI view.
‍
View appearance and disappearance events
By default, Instabug will automatically log whenever one of our UIKit-based view controllers appeared on the screen, and since SwiftUI still uses UIViewController subclasses under the hood to manage our overall UI and its navigation stack, we can still use those automatic events even if we’re no longer creating any UIViewController instances ourselves.
To make our Instabug timelines even nicer, though, let’s also log a separate appearance event for each of our top-level SwiftUI views. That way, we’ll be able to set custom names for each of our app’s various screens, which will make it much easier to get an overview of how a user navigated through our app before encountering a bug or a crash.
Let’s start by extending SwiftUI’s View protocol with a method that’ll let us identify a given view as a named screen, and whenever that view appears, we’ll call Instabug’s logViewDidAppearEvent API to report its name — like this:
‍
‍
With the above in place, we’ll now be able to easily connect each of our top-level views to our Instabug reports, which will make it much easier to see what views that were involved in each reported issue. For example, here we’re identifying a HomeListView as our app’s “Home” screen:
‍
‍
Instabug also offers full support for logging custom user events as well — so let’s also add a second argument to our identifyAsScreen method, which will let us automatically log a given event as soon as a view disappeared:
‍
‍
Using the above, we can now log disappearance events (such as when an onboarding flow was dismissed and completed) simply by passing a disappearEventName when calling our identifyAsScreen method — like this:
‍
‍
Of course, none of the above steps are really needed in order to make Instabug produce rich bug reports for us, but a little bit of extra metadata can go a long way when investigating bugs and crashes, especially UI-related ones.
‍
Conclusion
Because Instabug includes such deep integrations with UIKit, it already works great within the context of SwiftUI as well, since much of SwiftUI is still built directly on top of UIKit-based classes like UIView and UIViewController.
But by adding a few tweaks here and there we can make the experience of using Instabug within SwiftUI-based apps even better — so I hope that you found the tips within this article useful, and feel free to reach out via either Twitter or email if you have any questions, comments or feedback.
Thanks for reading!