SwiftUI 7 新 WebView:金蛇出洞,网页江湖换新天
概述
崇祯年间,华山派武学虽盛,却在应对江湖新局时渐显颓势;如今 SwiftUI 江湖亦是如此 ——WWDC 25 之前,若要在 SwiftUI 中显示网页,开发者恰似袁承志初闯江湖,纵有一身本领,却苦无称手兵刃。
直到那柄 "金蛇剑" 般的全新 WebView 横空出世,才让网页显示之道豁然开朗。
在本篇武学大家谈中,各位少侠将学到如下内容:
- 一、往昔困局:华山旧功难破迷阵
- 二、金蛇出洞:WWDC 25 的 WebView 新法
-
- 初窥门径:基础网页加载
-
- 内功心法:WebPage 状态管理
-
- 奇门绝技:JS 交互与平台适配
-
- 三、江湖展望:金蛇之后再无钝剑
想得到那柄可以横扫武林的神兵利器金蛇剑吗?那还等什么?让我们马上开始寻“剑”之旅吧!
Let's go!!!;)
一、往昔困局:华山旧功难破迷阵
想当年,SwiftUI 自身并无网页显示的独门心法,开发者们只得借 UIKit 的 WKWebView 这柄 "钝剑",再辅以UIViewRepresentable
为鞘,方能勉强施展。
这般操作,犹如袁承志在华山练剑时,需先扎三年马步 —— 基础虽牢,却失之滞涩。
且看这套 "华山入门剑法":
import SwiftUI
import WebKit
// 以UIViewRepresentable为桥,连接SwiftUI与WKWebView
struct WebViewWrapper: UIViewRepresentable {
let url: URL?
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard let url = url else { return }
uiView.load(URLRequest(url: url))
}
}
// 实战时需如此调用,恰似执钝剑闯敌营
struct ContentView: View {
var body: some View {
WebViewWrapper(url: URL(string: "https://apple.com"))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
这套功夫虽能御敌,却有三大弊端:
- 其一,
updateUIView
反复调用时易生错乱,好比剑法中 "剑招互碍"; - 其二,网页状态监听需另设代理,犹如练剑时还要分心护脉;
- 其三,与 SwiftUI 状态管理结合时,常现 "内力相冲" 之象 —— 稍有不慎便会数据错乱。
二、金蛇出洞:WWDC 25 的 WebView 新法
正当开发者们困于旧法之时,WWDC 25 恰似一场武林大会,苹果突然亮出 "金蛇剑"——SwiftUI 原生 WebView 横空出世!此剑一出,如金蛇郎君夏雪宜重现江湖,招式灵动,浑然天成,将网页显示之道推向新境。
1. 初窥门径:基础网页加载
新 WebView 的基础用法,恰似袁承志初得金蛇剑时的随手一挥,看似简单却暗藏玄机:
import SwiftUI
import WebKit
struct ContentView: View {
// 直接使用URL初始化,无需繁琐包装
var body: some View {
WebView(url: URL(string: "https://apple.com"))
.navigationTitle("金蛇洞")
.edgesIgnoringSafeArea(.bottom)
}
}
这般代码,较之旧法省去近八成冗余,正如金蛇剑法 "险、奇、快" 之妙 —— 无需再写UIViewRepresentable
的桥接代码,无需手动管理 WebView 生命周期,SwiftUI 自会料理妥当。
真乃呜呼快哉!
2. 内功心法:WebPage 状态管理
若要深入掌控网页状态,需修习 "金蛇秘籍"——WebPage
类。此物如同袁承志从山洞中所得的金蛇锥谱,将网页的标题、URL、加载进度等信息尽收其中:
import SwiftUI
import WebKit
internal import Combine
// 实战运用:将心法与招式结合
struct ContentView: View {
@State private var page = WebPage()
@State private var id: WebPage.NavigationID?
@State private var isLoading = false
@State private var event: WebPage.NavigationEvent?
var body: some View {
NavigationStack {
WebView(page)
.navigationTitle(page.title)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: {
page.reload()
}) {
Image(systemName: "arrow.counterclockwise")
}
}
ToolbarItem(placement: .topBarLeading) {
if isLoading {
ProgressView()
}
}
}
.onChange(of: page.isLoading) { _, new in
isLoading = new
}
.onReceive(page.currentNavigationEvent.publisher) { event in
guard event.navigationID == id else { return }
switch event.kind {
case let .failed(underlyingError: error):
print(error.localizedDescription)
case .finished:
print("网页加载完毕")
default:
break
}
}
.task {
let request = URLRequest(url: .init(string: "https://blog.csdn.net/mydo")!)
id = page.load(request)
}
}
}
}
此处关键在于@Published
属性 —— 当网页标题变化时,导航栏会如响应内力般自动更新;进度条则像金蛇吐信般实时菊花旋转:
这般状态同步,较旧法中手动绑定NotificationCenter
的操作,可谓 "化繁为简,返璞归真"。
3. 奇门绝技:JS 交互与平台适配
新 WebView 最令人称道之处,莫过于对 JavaScript 交互的 "化骨绵掌" 式处理。
昔日要在 Swift 与 JS 间传递数据,需写十数行桥接代码,如同袁承志在华山与温家五老缠斗时的狼狈;如今却能一剑破局:
// 实战运用:将心法与招式结合
struct ContentView: View {
@State private var page = WebPage()
@State private var id: WebPage.NavigationID?
@State private var isLoading = false
@State private var event: WebPage.NavigationEvent?
@State private var titleFromJS: String?
var body: some View {
NavigationStack {
WebView(page)
.navigationTitle(page.title)
.toolbar {
ToolbarItemGroup {
Button {
Task {
if let jsResult = try? await page.callJavaScript(
"""
return document.title;
"""
), let title = jsResult as? String {
titleFromJS = title
}
}
} label: {
Image(systemName: "figure.run")
}
Button {
Task {
try? await page.callJavaScript(
"""
document.body.style.backgroundColor = 'gold'
"""
)
}
} label: {
Image(systemName: "figure.cricket")
}
Button(action: {
page.reload()
}) {
Image(systemName: "arrow.counterclockwise")
}
}
ToolbarItem(placement: .topBarLeading) {
if isLoading {
ProgressView()
}
}
}
.onChange(of: page.isLoading) { _, new in
isLoading = new
}
.onReceive(page.currentNavigationEvent.publisher) { event in
guard event.navigationID == id else { return }
switch event.kind {
case let .failed(underlyingError: error):
print(error.localizedDescription)
case .finished:
print("网页加载完毕")
default:
break
}
}
.task {
let request = URLRequest(url: .init(string: "https://blog.csdn.net/mydo")!)
id = page.load(request)
}
.safeAreaInset(edge: .top) {
if let title = titleFromJS {
Text(title)
.font(.title2)
.padding()
.background(.thinMaterial.opacity(0.66), in: RoundedRectangle(cornerRadius: 15))
}
}
}
}
}
在上面的代码中,我们用 JavaScript 做了两件事:
- 动态实时获取到了网页的标题;
- 将网页背景设置为金色;
此等操作,恰似金蛇郎君以金蛇锥破敌甲胄 —— 直接穿透 Swift 与 JS 的壁垒。
更妙者,新 WebView 对各平台特性的适配,如 visionOS 的 "看向滚动" 功能,只需一行修饰符即可大功告成:
WebView(webView: page.webView)
#if os(VisionOS)
// 开启 VisionOS 滚动输入”通天眼“
.webViewScrollInputBehavior(.enabled, for: .look)
#endif
.scrollBounceBehavior(.basedOnSize) // 滚动反馈如"踏雪无痕"
三、江湖展望:金蛇之后再无钝剑
回首 SwiftUI 的网页显示之道,恰似袁承志的武学进阶:从华山派的循规蹈矩,到金蛇剑法的灵动不羁,再到融会贯通自成一派。
WWDC 25 推出的新 WebView,不仅解决了旧法中的 "招式沉冗" 之弊,更将 SwiftUI 的声明式编程理念推向新高度。
正如金蛇剑在袁承志手中终成一代传奇,这套新 WebView API 亦将成为开发者闯荡网页江湖的不二之选。
毕竟,真正的好功夫,从来都是 "大道至简,大巧若拙"—— 能以三两行代码办妥之事,何必耗费十数行力气?此乃 WWDC 25 留给 SwiftUI 开发者的最大启示,亦是江湖不变之真理。
那么,各位少侠看到这里又作何感想呢?
感谢宝子们的观看,再会啦!8-)