Here at PSPDFKit, we believe in continuous improvement, and one of our company’s core values is to explore, be creative, and be willing to embrace the latest technologies. It’s a crucial part of how we work, and we regularly take the time to experiment with things that we wouldn’t touch otherwise. Recently, we took an opportunity to rewrite a part of our PSPDFKit Catalog example project in SwiftUI to see how far we could go and where it would bring us. This blog post describes our experience, the challenges we faced, the lessons we learned, and the outcome we ended up with.
Let me immediately get this out of the way. SwiftUI excels at what it does best — creating a non-trivial UI with relatively low effort and state management that’s easy to reason about. Rewriting our list of PSPDFKit Catalog examples in SwiftUI, with most of its specialized behaviors, took us a bit more than a day.
Having the code on one side immediately update the Previews on the other side is extremely helpful for fine tuning, and this allowed us to iterate on our ideas faster than if we were to recompile our huge Xcode workspace, even with the build-time optimizations we have.
That being said, during the course of our experiment, we faced a number of obstacles that indicated an ongoing limit to SwiftUI’s production-grade maturity.
We’ve been developing PSPDFKit and PSPDFKitUI primarily in UIKit for years, so it doesn’t come as a surprise that shifting our deeply established paradigm to a more declarative one required time and effort.
Figuring out how to debug our freshly baked views was especially difficult. We have a lot of experience inspecting UIKit view hierarchies, but far less of it when it comes to SwiftUI. We mostly used trial and error to see what worked and how changes in state are propagated through the view hierarchy. The hidden
_printChanges function also proved useful a couple of times.
One of the best features of SwiftUI is the Previews and the ability to see a view along with its source code side by side. That is… if it works. Not long after adding the first SwiftUI view to our Xcode workspace, we noticed that Xcode would always get stuck in the Preparing iPad Simulator for Previews state, and then it’d fail with an ambiguous error. Previews worked in sample projects, but not in our workspace, so we were probably doing something wrong.
Upon closer inspection of the Build for Previews entries in the Reports navigator, we narrowed this problem down to a misconfiguration of our scheme. When building for Previews, Xcode will compile the currently active scheme for running. It turned out we had some test target dependencies that shouldn’t be there, and they failed to compile because the expected build configuration didn’t match.
In addition, we found that using Previews with a framework scheme (so, without an executable set for a Run action) was less reliable than when having an app scheme be active. After a bit of gardening, we eventually got the Previews working in our Xcode workspace.
Since its original release in 2019, SwiftUI has undergone a lot of major changes in the core of its design. At every following WWDC, Apple has introduced lots of new APIs and has completely revamped parts of the existing APIs. Because SwiftUI ships with the system frameworks, and not with each individual app, these new and improved APIs are locked out for earlier iOS versions.
This is a non-issue for apps that only support the latest version of iOS. However, with the n-1 system version compatibility strategy we have in place for PSPDFKit, this introduces a significant amount of conditional code and, in some cases, prevents us from using a feature at all.
One such example is the
.searchable view modifier that adds a search bar to the top of a
UISearchController — UIKit’s equivalent of that feature — has been available since iOS 8, the
.searchable view modifier requires iOS 15. We definitely wanted to preserve the search feature in PSPDFKit Catalog after rewriting it in SwiftUI. So, in this situation, we would’ve needed to either exclude it on iOS 14, or reimplement it manually for that version, which is an unreasonably complex task, since there’s no equivalent of
UISearchTextField in SwiftUI.
For APIs that were renamed or changed in a backward-incompatible way, we implemented Dave DeLong’s excellent
Backport mechanism, which helped us clean up a bunch of call sites. Sadly, this didn’t prevent us from encountering several SwiftUI limitations, such as the action sheets not working on iPadOS 13, and the
cell property not having an equivalent in SwiftUI.
These shortcomings were a major nail in the coffin for our adoption of SwiftUI in PSPDFKit Catalog’s list of examples.
Apple has been continuously improving its SwiftUI documentation, adding new articles and commendably extending the existing ones with every minor version of iOS. However, even though the major building blocks are described thoroughly, the less-used views and view modifiers received less love. Sometimes, the API documentation didn’t provide enough explanation to answer our questions. In these cases, we turned our attention to third-party online resources.
Admiringly, the iOS developer community has produced a lot of amazing books, video series, articles, and entire websites dedicated largely to SwiftUI over the past three years. These resources provided us with invaluable insight into the principles of SwiftUI and gave many answers to our specialized questions.
Nonetheless, with multiple different APIs, each working on only a subset of iOS versions, we noticed that some online resources — Stack Overflow answers in particular — are often outdated, use deprecated APIs, promote patterns that have later been discouraged, or suggest solutions that are indistinguishable from hacks. This is absolutely not the fault of the authors, who are in no way required to keep track of the release notes and go back to change the content they once produced. The fragmented documentation is, however, a problem that all of us SwiftUI developers need to watch out for.
PSPDFKit Catalog is built almost entirely in UIKit. Therefore, our task of rewriting the list of examples in SwiftUI required us to create a SwiftUI view that would interoperate with existing UIKit infrastructure.
This turned out to be easier said than done. At first, we implemented the navigation stack fully in SwiftUI using
NavigationView, but this approach bit us, as parts of our UIKit code responsible for presenting examples expected direct access to the underlying navigation controller.
To prevent the rewrite from cascading into parts of the example project we didn’t want to change, we eventually decided to subclass a
UIHostingController and split the implementation between SwiftUI and UIKit. Features that misbehaved in SwiftUI ended up remaining a part of UIKit.
Eventually, due to the bidirectional communication that needed to be set up between the two frameworks, our view controller subclass became nearly the size of its SwiftUI counterpart, and our pull request ended up having more additions than deletions.
This was the final deathblow for our experiment: We ended up with more code that did less.
At PSPDFKit, we believe that keeping up to date with modern technologies is crucial for providing the best experience for our customers. However, we’re also convinced that adopting the latest and greatest shouldn’t come at the expense of quality.
SwiftUI is marketed as a framework for building great-looking apps with as little code as possible. During the course of our experimentation, we found that SwiftUI broke this promise by making simple things quite difficult and complex things nearly impossible to achieve.
With that in mind, we decided not to ship the rewritten version of PSPDFKit Catalog, and to stick with a pure UIKit stack for the time being. This doesn’t mean we won’t be using SwiftUI in PSPDFKit. Quite the opposite — we’ll use what we learned to ship better SwiftUI APIs in the future, and we’ll try to not repeat Apple’s mistakes on that front. We’re excited to see where things are headed, and we can’t wait to explore using SwiftUI in our Catalog app again in the future, once the roadblocks have been removed.