This article introduces tips for presenting UIViewController
in SwiftUI.
While this might seem trivial, I’ll share the technique as there are no articles currently explaining it.
A Common Pattern Using UIViewController in SwiftUI
In general, UIViewControllerRepresentable
is used when UIViewController
is needed in SwiftUI.
struct FooView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> FooViewController {
FooViewController()
}
func updateUIViewController(_ uiViewController: FooViewController, context: Context) {
// Update the view state
}
}
This is a straightforward implementation, but it's a bit cumbersome to define a new subtype of UIViewControllerRepresentable
for each UIViewController
.
And it's also a bit cumbersome for me to name the subtype.
Use Generics
To solve the issue, define a generic type.
This is an implementation example.
The code is a bit long, but closures corresponding to each function of UIViewControllerRepresentable
are just injected.
public struct UIViewControllerRepresenter<ViewController: UIViewController, Coordinator>: UIViewControllerRepresentable {
private let makeCoordinatorHandler: @MainActor () -> Coordinator
private let makeUIViewControllerHandler: @MainActor (Context) -> ViewController
private let updateUIViewControllerHandler: @MainActor (ViewController, Context) -> Void
private let sizeThatFitsHandler: @MainActor (ProposedViewSize, ViewController, Context) -> CGSize?
public init(
makeCoordinator: @escaping @MainActor () -> Coordinator = { () },
makeUIViewController: @escaping @MainActor (Context) -> ViewController,
updateUIViewController: @escaping @MainActor (ViewController, Context) -> Void = { _, _ in },
sizeThatFits: @escaping @MainActor (ProposedViewSize, ViewController, Context) -> CGSize? = { _, _, _ in nil }
) {
self.makeCoordinatorHandler = makeCoordinator
self.makeUIViewControllerHandler = makeUIViewController
self.updateUIViewControllerHandler = updateUIViewController
self.sizeThatFitsHandler = sizeThatFits
}
public func makeCoordinator() -> Coordinator {
makeCoordinatorHandler()
}
public func makeUIViewController(context: Context) -> ViewController {
makeUIViewControllerHandler(context)
}
public func updateUIViewController(_ viewController: ViewController, context: Context) {
updateUIViewControllerHandler(viewController, context)
}
@MainActor
public func sizeThatFits(_ proposal: ProposedViewSize, uiViewController: ViewController, context: Context) -> CGSize? {
sizeThatFitsHandler(proposal, uiViewController, context)
}
}
Usage
Just use the type when you want to present UIViewController
in SwiftUI.
You don't need to define new types.
struct BarView: View {
var body: some View {
UIViewControllerRepresenter { _ in
FooViewController()
}
}
}
In a case where a coordinator is required or you need to do something on update, closures for them can be injected.
struct BarView: View {
var body: some View {
UIViewControllerRepresenter {
FooCoordinator()
} makeUIViewController: { context in
let viewController = FooViewController()
viewController.delegate = context.coordinator
return viewController
} updateUIViewController: { _, _ in
print("updated")
}
}
}
class FooCoordinator: FooDelegate {
// ...
}
Note
This way is just for omitting of type definition and it doesn't cover all use cases.
For example, since the dismantleUIViewController
function in UIViewControllerRepresentable
is static, this method doesn’t cover cases where that function is needed.
Wrap up
In this article, I introduced a technique for effortlessly presenting UIViewController
in SwiftUI.
This approach doesn’t cover all use cases, but it’s a powerful utility.
I hope this article can help your development!
Top comments (0)