SwiftUI가 2019년 등장 이후, 만 6년만에 SwiftUI 네이티브 웹뷰 API를 지원하기 시작했다. 감격스러워서 눈물이 나올 지경이다.
물론, 아쉽게도 모든 버전에서 사용 가능한 기능은 아니다. iOS 26 이상부터 사용 가능하다.
이전까지는 SwiftUI 용 웹뷰를 지원하지 않았기 때문에, 어쩔 수 없이 UIKit에서 제공하는 WKWebView와 UIViewRepresentable를 활용하여 간접적으로 SwiftUI에서 웹뷰를 구현할 수 있었다.
아래는 UIKit에서 제공하는 WKWebView를 활용하여 웹뷰를 구현하는 간단한 예시이다.
import SwiftUI
import WebKit
struct BaseWebView: UIViewRepresentable {
private let webView = WKWebView()
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
func makeUIView(context: Context) -> some UIView {
webView.load(URLRequest(url: URL(string: "https://www.google.com")!))
return webView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
class Coordinator: NSObject, WKNavigationDelegate {
private let parent: BaseWebView
init(parent: BaseWebView) {
self.parent = parent
}
}
}
이번에는 iOS 26부터 추가된 SwiftUI 전용 WebView를 활용하여 웹뷰를 구현하면 아래와 같이 코드를 작성할 수 있다.
import SwiftUI
import WebKit
struct ContentView: View {
var body: some View {
WebView(url: URL(string: "https://www.google.com"))
}
}
UIKit의 WKWebView을 사용할 때 보다 단 5줄이면 웹뷰 구현을 끝낼 수 있어 생산성을 매우 높일 수 있을 것이라 생각한다.
이번에는 WKNavigationDelegate에서 제공 했었던 다양한 웹뷰 기능을 활용해 보자.
초기 세팅은 아래와 같은데, 먼저 WebPage 인스턴스를 관찰 가능한 형태로 선언한다.
import Combine
import WebKit
@MainActor
@Observable
final class ContentViewModel {
let page: WebPage = WebPage()
func load() {
page.load(URLRequest(url: URL(string: "https://www.google.com")!))
}
}
그리고 View 쪽에서 WebView의 생성자 전달인자로 WebPage 인스턴스를 전달한다.
import SwiftUI
import WebKit
struct ContentView: View {
@Environment(ContentViewModel.self) private var viewModel
var body: some View {
WebView(viewModel.page)
.onAppear {
viewModel.load()
}
}
}
이제 WebPage에서 제공하는 다양한 데이터를 활용하여 기존 WKNavigationDelegate에서 제공하는 다양한 웹뷰 기능을 구현할 수 있다. 아래에서 간단하게 몇가지 기능을 소개한다.
WebPage의 estimatedProgress는 현재 로딩 중인 페이지의 progress 상태를 Double 값으로 제공한다. 그리고 isLoading은 웹 페이지의 로딩 여부를 Bool 값으로 제공한다.
두 프로퍼티를 활용하여 SwiftUI의 ProgressView와 함께 사용하면 웹뷰의 로딩 바를 구현할 수 있다.
VStack(spacing: 0) {
ProgressView(value: viewModel.page.estimatedProgress)
.opacity(viewModel.page.isLoading ? 1 : 0)
WebView(viewModel.page)
.onAppear {
viewModel.load()
}
}
WebPage에서 제공하는 javascript 인젝션을 활용하면 웹 페이지의 데이터를 가져오거나 데이터를 런타임에 동적으로 변경할 수 있다.
예를 들어, 아래와 같이 callJavaScript(_:) 메서드를 활용하면 웹 페이지 내의 롱터치 기능을 비활성화하는 JavaScript 코드를 실행할 수 있다.
import Combine
import WebKit
@MainActor
@Observable
final class ContentViewModel {
let page: WebPage = WebPage()
func injectScript() {
Task {
do {
try await page.callJavaScript(
"""
document.documentElement.style.webkitUserSelect='none'
"""
)
} catch {
print(error)
}
}
}
}