Display a UIKit UIView inside a SwiftUI View
TLDR: Learn how to use UIViewRepresentable to display UIKit views inside SwiftUI. This tutorial demonstrates wrapping WKWebView in SwiftUI with a progress indicator, perfect for iOS developers transitioning to SwiftUI.
SwiftUI can display a UIKit UIView. Adapting UIViews keeps UIKit programming to a minimum by allowing SwiftUI
views to supplement UIViews. For example, a WKWebView with an overlaid
ProgressView.
How do I create the SwiftUI View?
The WebView declares a ZStack, and places the ProgressView on top of the
WebViewSwiftUIAdapter at the bottom of the page.
The WebView owns the estimatedProgress and is annotated with the
@State property wrapper.
struct WebView: View {
@State var estimatedProgress: Float = 0.0
let url: URL
var body: some View {
ZStack(alignment: .bottomLeading) {
WebViewSwiftUIAdapter(estimatedProgress: $estimatedProgress, url: url)
if estimatedProgress < 1.0 {
ProgressView(value: estimatedProgress)
.frame(maxWidth: .infinity)
.background(.white)
}
}
}
}
How do I implement UIViewRepresentable?
The SwiftUIAdapter suffix can be used to identify any Swift UI views that wrap a
UIView.
These adapters implement UIViewRepresentable.
UIViewRepresentable has two functions requiring implementation. SwiftUI calls
makeUIView when it creates a view. SwiftUI calls updateUIView when it updates the
view.
The makeCoordinator function returns a Coordinator. A Coordinator
instance is of an associatedtype that allows communication between SwiftUI and UIKit.
struct WebViewSwiftUIAdapter: UIViewRepresentable {
@Binding var estimatedProgress: Float
let url: URL
let webView = WKWebView()
func makeCoordinator() -> WebViewCoordinator {
WebViewCoordinator(self)
}
func makeUIView(context: Context) -> WKWebView {
let request = URLRequest(url: url)
webView.load(request)
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {}
}
How does the Coordinator work?
The WebViewCoordinator has a reference to the parent WebViewSwiftUIAdapter. The
WebViewCoordinator can interact with the parent's webView and
estimatedProgress properties.
The WebViewCoordinator subscribes to estimatedProgress updates from the
webView. When the webView estimatedProgress changes, the
WebViewCoordinator updates the parent's estimatedProgress. Since the parent's
estimatedProgress is a Binding to the SwiftUI view's State property,
SwiftUI updates the view.
class WebViewCoordinator: NSObject {
let parent: WebViewSwiftUIAdapter
var cancellable: AnyCancellable?
init(_ parent: WebViewSwiftUIAdapter){
self.parent = parent
super.init()
cancellable = parent.webView.publisher(for: \.estimatedProgress)
.receive(on: RunLoop.main)
.sink{ [weak self] in
self?.parent.estimatedProgress = Float($0)
}
}
}