Last updated 4 min read

Use SwiftUI With UIKit – WWDC2022

How to bridge data between the legacy app and SwiftUI back and forth. How to embed SwiftUI into UICollectionView and UITableView with UIHostingConfiguration. Here is the summary for the "Use SwiftUI With UIKit" session from WWDC22.
#iOS App Development

To be honest, from this talk I was expecting to get something more. Meaning that... well we did know that SwiftUI view is wrapped into and its lifecycle is managed by UIHostingViewController. And we did know that ObservableObjects trigger SwiftUI refresh...

Anyway, here is the summary.

/content/images/2022/06/Screenshot-2022-06-19-at-12.58.02.png

In iOS16 new self-sizing options for UIHostingViewController were added, allowing it to be added as a child view controller as we did with plain old view controllers.

/content/images/2022/06/Screenshot-2022-06-19-at-13.00.33.png

Bridging Data from App to SwiftUI

There are at least two ways to update the views hosted inside UIHostingViewController. The first one is manual:

/content/images/2022/06/Screenshot-2022-06-19-at-13.07.49.png

The second way to keep the UI in sync is to use ObservableObjects. First, we make our model to ObservableObject protocol and pass it to SwiftUI view as a dependency:

/content/images/2022/06/Screenshot-2022-06-19-at-13.10.40.png

Then we pass it to the SwiftUI view when creating a host view controller:

/content/images/2022/06/Screenshot-2022-06-19-at-13.11.29.png

SwiftUI in Cells

UIHostingConfiguration

The brand new UIHostingConfiguration feature allows to host SwiftUI views inside UICollectionView and UITableVIewCells:

/content/images/2022/06/Screenshot-2022-06-19-at-13.12.36.png

Looks like somewhere under the hood it creates and reuses UIHostingViewController with sizingOptions assigned to .preferredContentSize.

/content/images/2022/06/Screenshot-2022-06-19-at-13.15.51.png

The following videos are recommended to watch:

  • [[Modern cell configuration - WWDC2020]]
  • [[Hello Swift Charts - WWDC2022]]

Margins

An important point is how UIKit cells with embedded SwiftUI views handle margins for its content. There is some default value applied, but allows to configure it by using margins(...) modifier:

/content/images/2022/06/Screenshot-2022-06-19-at-13.31.36.png

Background

There is also a way to paint the background with (quite unexpectedly) background modifier:

/content/images/2022/06/Screenshot-2022-06-19-at-14.59.55.png

Here is how the background modifier affects the cell (quite expectedly):

/content/images/2022/06/Screenshot-2022-06-19-at-13.32.53.png

Swipes

SwiftUI cells hosting configuration also allows handling swipes. We were warned 100 times to avoid using IndexPath in handlers because it will lead to a mess due to the cells' reusable nature. That's what we knew before, didn't we?

/content/images/2022/06/Screenshot-2022-06-19-at-13.33.59.png

Configuration update handler

Here is an example of how to use configurationUdpateHandler with UIHostingConfiguration:

/content/images/2022/06/Screenshot-2022-06-19-at-13.39.29.png

Cells Data flow

Apple's recommendation is to use DiffableDataSource only for handling collection updates like insertion, deletion, and item moves by using items' ids.

/content/images/2022/06/Screenshot-2022-06-19-at-15.20.02.png

For individual items updates handling, they recommend using ObservableObject.

/content/images/2022/06/Screenshot-2022-06-19-at-15.20.14.png

This is all quite understandable.

It's possible to use UICollectionViewDataSource not only with items' ids but to treat the entire item as a Hashable thing. This will allow not only to handle collection-wise updates but also individual items refresh. In that case, the changed item would be actually treated as deleted and inserted back.

And this is a very reliable way to handle collections in sync with the UI simply because there is no place for mistakes: you always show what you have in the collection.

The downside of this solution is that it causes the cell to reload completely. To be more exact, it triggers the cell to deletion and re-insert.

When using a pure UIKit cell that was affordable, at least I've never faced any issues with that. However, with cells backed with SwiftUI, it looks like not. Anyway, this is expectable, because every UIHostingConfiguration cell is likely to have an entire UIHostingViewController under the hood.

Hopefully, a brand new A17 Bionic chip would be capable to handle that, lol.

Self-sizing cells

The most wanted feature since iOS8 is still with us.

/content/images/2022/06/Screenshot-2022-06-19-at-13.51.35.png

Another recommendation to watch: What's new in UIKit – WWDC2022

Bridging Data from SwiftUI to UIKit

The provided way to handle mutation made in SwiftUI is simply ObservableObject's 2-ways binding:

/content/images/2022/06/Screenshot-2022-06-19-at-15.38.33.png
/content/images/2022/06/Screenshot-2022-06-19-at-15.40.53.png

Key Takeaways

  • Use UIHostingViewController to host SwiftUI views
  • Use UIHostingConfiguration to host SwiftUI views in Cells
  • Use observable objects for 2-way data binding

References

Use SwiftUI with UIKit