מבוא: למה WebView מקורי ב-SwiftUI משנה את כללי המשחק
בואו נודה באמת — מאז ש-SwiftUI הוצג לראשונה ב-WWDC 2019, מפתחי iOS חיו עם חיסרון מעצבן אחד: אין תמיכה מקורית בהצגת תוכן אינטרנט. כל מי שרצה להטמיע דף אינטרנט באפליקציה נאלץ לעטוף את WKWebView דרך UIViewRepresentable — וכל מי שעשה את זה יודע כמה זה מסורבל. בעיות סנכרון מצב, בעיות ביצועים, זליגות זיכרון... במשך שש שנים, תוכן אינטרנט נשאר "אזרח סוג ב" בעולם של SwiftUI.
וב-WWDC 2025? הכל השתנה.
Apple חשפה רכיב WebView מקורי לחלוטין עבור SwiftUI ב-iOS 26. לא עוד עטיפה סביב רכיבי UIKit, אלא ממשק שתוכנן מהיסוד לעבוד עם הפרדיגמה הדקלרטיבית של SwiftUI ועם תכונות Swift מודרניות כמו Observation framework. ולצד WebView הגיע גם WebPage — מודל Observable שנותן שליטה מלאה על מצב דף האינטרנט, ניווט, הרצת JavaScript ועוד.
במדריך הזה נעבור על כל מה שצריך לדעת: שימוש בסיסי, בניית דפדפן מלא, הרצת JavaScript, טעינת HTML מקומי, הגדרות תצורה, Modifiers ייעודיים, השוואה לגישה הישנה, תמיכה בגרסאות ישנות ושיטות עבודה מומלצות. אז בואו נצלול פנימה.
שימוש בסיסי ב-WebView
הדרך הפשוטה ביותר להציג תוכן אינטרנט ב-SwiftUI עם iOS 26? שלוש שורות קוד. פשוט מייבאים את WebKit ומשתמשים ב-WebView עם כתובת URL:
import SwiftUI
import WebKit
struct ContentView: View {
var body: some View {
WebView(url: URL(string: "https://www.apple.com")!)
}
}
זהו. שלוש שורות והאפליקציה שלכם מציגה דף אינטרנט מלא עם תמיכה מובנית במחוות גלילה, ניווט קדימה ואחורה, וזום. כן, באמת ככה פשוט.
שימו לב ש-WebView מקבל URL אופציונלי (URL?), כך שאין צורך לטפל בנפרד במקרים שבהם הכתובת לא תקינה. בגרסה הבסיסית הזו, הוא מטפל אוטומטית בטעינת העמוד, מציג את התוכן ומאפשר אינטראקציה בסיסית. אבל — אין לכם שליטה על מצב הטעינה, הכותרת, ההיסטוריה או הרצת JavaScript.
לשליטה מתקדמת יותר, נצטרך את WebPage.
WebPage לשליטה מתקדמת
WebPage הוא מחלקה שמציית לפרוטוקול Observable, מה שהופך אותו לשותף מושלם עבור SwiftUI. הוא מייצג את מצב דף האינטרנט ונותן גישה מלאה לטעינה, ניווט, מאפייני העמוד והרצת JavaScript. בקיצור — עם WebPage אפשר לבנות חוויות גלישה עשירות ומותאמות אישית באמת.
יצירת WebPage וטעינת עמוד
הדפוס המומלץ הוא ליצור מופע מקומי של WebPage כ-@State ולטעון את כתובת ההתחלה ב-onAppear:
import SwiftUI
import WebKit
struct ContentView: View {
@State private var page = WebPage()
var body: some View {
WebView(page)
.onAppear {
page.load(URLRequest(url: URL(string: "https://apple.com")!))
}
}
}
שימו לב להבדל החשוב כאן: בעוד WebView(url:) מקבל URL? ישירות, WebPage משתמש ב-load(URLRequest(url:)) שדורש URLRequest. זה אולי נראה כמו צעד נוסף, אבל הגישה הזו מאפשרת גמישות רבה יותר — למשל, הוספת headers מותאמים לבקשה.
מאפיינים Observable של WebPage
אחד הדברים הכי טובים ב-WebPage הוא שהוא נותן מאפיינים שמתעדכנים אוטומטית ומפעילים עדכוני UI ב-SwiftUI. המאפיינים המרכזיים:
page.title— כותרת העמוד הנוכחי (מתעדכנת בכל ניווט)page.url— כתובת ה-URL הנוכחיתpage.estimatedProgress— אחוז הטעינה המשוער (ערך בין 0.0 ל-1.0)page.isLoading— האם העמוד נמצא בתהליך טעינה
מכיוון שמאפיינים אלה הם Observable, כל שינוי בהם מתעדכן אוטומטית בממשק. אין צורך ב-KVO, אין צורך ב-Bindings ידניים — הכל פשוט עובד. הנה דוגמה שמציגה את כותרת העמוד ומד התקדמות:
struct WebBrowserView: View {
@State private var page = WebPage()
var body: some View {
VStack(spacing: 0) {
Text(page.title ?? "טוען...")
.font(.headline)
.lineLimit(1)
.padding(.horizontal)
if page.isLoading {
ProgressView(value: page.estimatedProgress, total: 1.0)
.tint(.blue)
}
WebView(page)
}
.onAppear {
page.load(URLRequest(url: URL(string: "https://apple.com")!))
}
}
}
שיטות טעינה וניווט
WebPage מספק מספר שיטות לטעינה וניווט:
page.load(URLRequest(url:))— טעינת כתובת URL חדשהpage.load(html:baseURL:)— טעינת תוכן HTML ישירותpage.reload()— רענון העמוד הנוכחיpage.stopLoading()— עצירת הטעינה
בניית ממשק דפדפן מלא
עכשיו כשאנחנו מבינים את WebPage, הגיע הזמן לעשות משהו מעניין באמת. בואו נבנה ממשק דפדפן שלם עם סרגל כלים — כולל כותרת, סרגל התקדמות, כפתורי חזרה וקדימה, כפתור רענון וכפתור שיתוף.
הבנת BackForwardList
כאשר המשתמש גולש בין עמודים, WebPage שומר את היסטוריית הניווט ב-backForwardList. הרשימה כוללת שני מאפיינים עיקריים:
page.backForwardList.backList— מערך של עמודים שהמשתמש ביקר בהם קודם (לא כולל הנוכחי)page.backForwardList.forwardList— מערך של עמודים שניתן לנווט אליהם קדימה (אם חזרו אחורה)
כדי לנווט לעמוד מההיסטוריה, משתמשים ב-page.load(item) כאשר item הוא אלמנט מתוך BackForwardList.
דוגמה מלאה: דפדפן עם סרגל כלים
import SwiftUI
import WebKit
struct FullBrowserView: View {
@State private var page = WebPage()
var body: some View {
NavigationStack {
VStack(spacing: 0) {
// Progress bar
if page.isLoading {
ProgressView(value: page.estimatedProgress, total: 1.0)
.tint(.accentColor)
}
// WebView
WebView(page)
.ignoresSafeArea(edges: .bottom)
}
.navigationTitle(page.title ?? "טוען...")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
// Back button
Button {
if let lastItem = page.backForwardList.backList.last {
page.load(lastItem)
}
} label: {
Image(systemName: "chevron.backward")
}
.disabled(page.backForwardList.backList.isEmpty)
// Forward button
Button {
if let firstItem = page.backForwardList.forwardList.first {
page.load(firstItem)
}
} label: {
Image(systemName: "chevron.forward")
}
.disabled(page.backForwardList.forwardList.isEmpty)
Spacer()
// Reload / Stop button
Button {
if page.isLoading {
page.stopLoading()
} else {
page.reload()
}
} label: {
Image(systemName: page.isLoading
? "xmark" : "arrow.clockwise")
}
// Share button
if let url = page.url {
ShareLink(item: url)
}
}
}
.onAppear {
page.load(URLRequest(url: URL(string: "https://apple.com")!))
}
}
}
}
מה שיפה כאן: כפתורי הניווט מושבתים אוטומטית כשאין היסטוריה זמינה, כפתור הרענון הופך לכפתור עצירה במהלך הטעינה, וכפתור השיתוף משתמש ב-ShareLink המובנה של SwiftUI. הכל דקלרטיבי ונקי.
תפריט היסטוריה מתקדם
רוצים חוויה עוד יותר מתקדמת? אפשר ליצור תפריט שמציג את כל ההיסטוריה בלחיצה ארוכה, בדומה לספארי:
struct BackForwardMenu: View {
let list: [WebPage.BackForwardList.Item]
let systemImage: String
let navigateToItem: (WebPage.BackForwardList.Item) -> Void
var body: some View {
Menu {
ForEach(list) { item in
Button(item.title ?? item.url.absoluteString) {
navigateToItem(item)
}
}
} label: {
Label("Navigate", systemImage: systemImage)
} primaryAction: {
if let first = list.first {
navigateToItem(first)
}
}
.disabled(list.isEmpty)
}
}
נקודה חשובה: יש בעיה ידועה שבה תפריטי ההיסטוריה לא תמיד מתעדכנים כשהרשימה משתנה. פתרון נפוץ הוא להוסיף id דינמי לתפריט שמשתנה עם כל טעינה — למשל, באמצעות page.isLoading.
הרצת JavaScript
כאן הדברים מתחילים להיות ממש מעניינים. אחת התכונות החזקות ביותר של WebPage היא היכולת להריץ קוד JavaScript על העמוד הנטען. השיטה callJavaScript היא אסינכרונית ומחזירה ערך, מה שהופך אותה לשילוב מושלם עם async/await של Swift.
שימוש בסיסי
struct JavaScriptExampleView: View {
@State private var page = WebPage()
@State private var pageTitle: String = ""
var body: some View {
VStack {
Text("כותרת מ-JavaScript: \(pageTitle)")
.font(.headline)
.padding()
WebView(page)
.onAppear {
page.load(URLRequest(url: URL(string: "https://apple.com")!))
}
Button("קבל כותרת") {
Task {
do {
let title = try await page.callJavaScript("document.title")
if let titleString = title as? String {
pageTitle = titleString
}
} catch {
print("שגיאת JavaScript: \(error)")
}
}
}
.padding()
}
}
}
דוגמאות מעשיות
הרצת JavaScript פותחת מגוון ענק של אפשרויות — חילוץ מידע מהעמוד, שינוי מראה ה-DOM, הזרקת פונקציונליות מותאמת אישית. הנה כמה דוגמאות שתוכלו להשתמש בהן מיד:
// Extract all links from the page
Task {
do {
let script = """
Array.from(document.querySelectorAll('a[href]'))
.map(a => ({ text: a.textContent.trim(), href: a.href }))
.filter(link => link.text.length > 0)
.slice(0, 10)
"""
let links = try await page.callJavaScript(script)
print("Found links: \(links ?? "none")")
} catch {
print("Error: \(error)")
}
}
// Modify the DOM - change background color
Task {
do {
try await page.callJavaScript(
"document.body.style.backgroundColor = '#f0f0f0'"
)
} catch {
print("Error: \(error)")
}
}
// Extract metadata from the page
Task {
do {
let script = """
JSON.stringify({
title: document.title,
description: document.querySelector(
'meta[name=\"description\"]'
)?.content ?? '',
url: window.location.href
})
"""
let metadata = try await page.callJavaScript(script)
if let jsonString = metadata as? String {
print("Page metadata: \(jsonString)")
}
} catch {
print("Error: \(error)")
}
}
חשוב לזכור: הרצת JavaScript היא אסינכרונית ועשויה לזרוק שגיאה, אז תמיד משתמשים ב-try await בתוך בלוק Task עם טיפול בשגיאות. ודבר נוסף שכדאי לשים לב אליו — אל תזריקו מחרוזות מהמשתמש ישירות לסקריפט. זה פותח את הדלת לפגיעויות הזרקת קוד. במקום זאת, העבירו ערכים דרך הפרמטר arguments: של המתודה.
טעינת תוכן HTML מקומי
לא תמיד צריך לטעון עמוד מהאינטרנט. לפעמים אתם רוצים להציג תוכן מותאם אישית, דוח, או תצוגה מקדימה של HTML — ו-WebPage תומך בזה בצורה מצוינת:
struct LocalHTMLView: View {
@State private var page = WebPage()
let htmlContent = """
שלום מ-SwiftUI!
כרטיס לדוגמה
זהו תוכן HTML שנטען מקומית
באמצעות WebPage.
"""
var body: some View {
WebView(page)
.onAppear {
page.load(html: htmlContent, baseURL: nil)
}
}
}
הפרמטר baseURL קובע את כתובת הבסיס עבור משאבים יחסיים בתוך ה-HTML. צריכים לטעון תמונות או קבצי CSS מקומיים? העבירו את כתובת התיקייה המתאימה. אם אין צורך במשאבים חיצוניים, פשוט תעבירו nil.
בנוסף ל-HTML ממחרוזת, אפשר גם לטעון נתונים גולמיים עם סוג MIME מוגדר באמצעות page.load(data:mimeType:characterEncodingName:baseURL:). זה מאפשר להציג סוגי תוכן שונים כמו PDF או SVG.
הגדרות תצורה של WebPage
אפשר להתאים אישית את ההתנהגות של WebPage באמצעות אובייקט WebPage.Configuration. נקודה חשובה — יש להגדיר את התצורה בזמן יצירת ה-WebPage. אי אפשר לשנות אותה אחר כך (למדתי את זה בדרך הקשה).
@State private var page: WebPage = {
var config = WebPage.Configuration()
// Custom user agent suffix
config.applicationNameForUserAgent = "MyApp/1.0"
// Allow AirPlay for media
config.allowsAirPlayForMediaPlayback = true
// Enable data detectors for phone numbers, links, etc.
config.dataDetectorTypes = [.phoneNumber, .link, .address]
return WebPage(configuration: config)
}()
אפשרויות תצורה עיקריות
applicationNameForUserAgent— מחרוזת שמתווספת ל-User Agent של הבקשות. שימושי לזיהוי האפליקציה שלכם בצד השרת.allowsAirPlayForMediaPlayback— קובע האם תוכן מדיה ניתן להפעלה דרך AirPlay. ברירת המחדל היאtrue.dataDetectorTypes— מגדיר אילו סוגי נתונים יזוהו אוטומטית כלינקים פעילים: מספרי טלפון, כתובות, לינקים, אירועי יומן ועוד.defaultNavigationPreferences— מאפשר להגדיר העדפות ניווט ברירת מחדל, כמו האם לאפשר תוכן JavaScript.
ניהול ניווט מותאם אישית
רוצים לשלוט באילו כתובות URL ה-WebView מורשה לגלוש? הפרוטוקול WebPage.NavigationDeciding מאפשר בדיוק את זה. שימושי למשל כשרוצים להגביל גלישה לדומיין מסוים:
struct DomainRestrictionDecider: WebPage.NavigationDeciding {
let allowedDomain: String
func decidePolicy(
for response: WebPage.NavigationResponse
) async -> WKNavigationResponsePolicy {
if response.response.url?.host?.contains(allowedDomain) == true {
return .allow
} else {
return .cancel
}
}
}
// Usage
@State private var page: WebPage = {
let decider = DomainRestrictionDecider(
allowedDomain: "apple.com"
)
return WebPage(navigationDecider: decider)
}()
Modifiers ייעודיים ל-WebView
כמו כל View אחר ב-SwiftUI, גם ל-WebView יש Modifiers ייעודיים שמאפשרים להתאים את התנהגותו. בואו נעבור עליהם.
webViewBackForwardNavigationGestures
שולט במחוות החלקה לניווט קדימה ואחורה. כברירת מחדל, המחוות מופעלות:
WebView(page)
.webViewBackForwardNavigationGestures(.enabled) // Default
// or
.webViewBackForwardNavigationGestures(.disabled)
ההמלצה שלי? תשאירו את המחוות האלה מופעלות. הן נותנות חוויה טבעית ומוכרת למשתמשי iOS.
webViewMagnificationGestures
מאפשר או מונע זום באמצעות מחוות צביטה (pinch-to-zoom):
WebView(page)
.webViewMagnificationGestures(.enabled)
שימושי כשמציגים תוכן שמותאם לגודל מסך ספציפי ואין צורך בזום, או להפך — כשרוצים לאפשר למשתמש להגדיל טקסט קטן.
webViewLinkPreviews
קובע האם לחיצה ארוכה על לינק מציגה תצוגה מקדימה של היעד:
WebView(page)
.webViewLinkPreviews(.enabled) // Default
// or
.webViewLinkPreviews(.disabled)
webViewContentBackground
הנה Modifier שאני במיוחד אוהב — הוא מאפשר להסתיר את צבע הרקע של דף האינטרנט ולהציג במקומו רקע מותאם של SwiftUI:
WebView(page)
.webViewContentBackground(.hidden)
.background(
LinearGradient(
colors: [.blue.opacity(0.1), .purple.opacity(0.1)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
שילוב מספר Modifiers
כמובן שאפשר לשלב כמה Modifiers יחד:
WebView(page)
.webViewBackForwardNavigationGestures(.enabled)
.webViewMagnificationGestures(.enabled)
.webViewLinkPreviews(.disabled)
.webViewContentBackground(.hidden)
.background(.ultraThinMaterial)
בנוסף, קיימים גם webViewTextSelection לשליטה בבחירת טקסט ו-webViewElementFullscreenBehavior לשליטה בתצוגת מסך מלא של אלמנטים כמו וידאו.
השוואה: WebView מקורי מול UIViewRepresentable הישן
כדי להבין באמת את המהפכה שה-WebView המקורי מביא, בואו נשווה את שתי הגישות זו לזו. ההבדל הוא דרמטי.
הגישה הישנה: UIViewRepresentable
struct LegacyWebView: UIViewRepresentable {
let url: URL
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.load(URLRequest(url: url))
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {}
}
// For advanced features, you needed a Coordinator:
struct LegacyWebViewAdvanced: UIViewRepresentable {
let url: URL
@Binding var title: String
@Binding var isLoading: Bool
@Binding var progress: Double
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.navigationDelegate = context.coordinator
webView.addObserver(context.coordinator,
forKeyPath: "title", options: .new, context: nil)
webView.addObserver(context.coordinator,
forKeyPath: "isLoading", options: .new, context: nil)
webView.addObserver(context.coordinator,
forKeyPath: "estimatedProgress", options: .new,
context: nil)
webView.load(URLRequest(url: url))
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {}
class Coordinator: NSObject, WKNavigationDelegate {
var parent: LegacyWebViewAdvanced
init(_ parent: LegacyWebViewAdvanced) {
self.parent = parent
}
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?
) {
guard let webView = object as? WKWebView else { return }
DispatchQueue.main.async {
self.parent.title = webView.title ?? ""
self.parent.isLoading = webView.isLoading
self.parent.progress = webView.estimatedProgress
}
}
}
}
הגישה החדשה: SwiftUI WebView מקורי
struct ModernWebView: View {
@State private var page = WebPage()
var body: some View {
VStack {
Text(page.title ?? "")
if page.isLoading {
ProgressView(value: page.estimatedProgress)
}
WebView(page)
}
.onAppear {
page.load(URLRequest(url: URL(string: "https://apple.com")!))
}
}
}
רואים את ההבדל? אותה פונקציונליות, פחות משליש מהקוד. בלי Coordinator, בלי KVO, בלי DispatchQueue.main.async.
טבלת השוואה מפורטת
| קריטריון | הגישה הישנה (UIViewRepresentable) | הגישה החדשה (SwiftUI WebView) |
|---|---|---|
| כמות קוד | עשרות שורות למימוש בסיסי, מאות לפונקציונליות מלאה | 3-5 שורות למימוש בסיסי, עשרות שורות לפונקציונליות מלאה |
| מעקב אחרי מצב | KVO ידני עם observeValue, סיכון לזליגות זיכרון | מאפיינים Observable אוטומטיים |
| סנכרון עם SwiftUI | דרוש Coordinator + @Binding ידני + DispatchQueue.main.async | אוטומטי באמצעות Observation framework |
| הרצת JavaScript | evaluateJavaScript עם callback/completion handler | async/await מקורי עם callJavaScript |
| ניהול זיכרון | סיכון ל-retain cycles בין Coordinator ל-WKWebView | מנוהל אוטומטית |
| מחוות ו-Modifiers | הגדרה ידנית דרך מאפייני WKWebView | Modifiers דקלרטיביים (.webViewBackForwardNavigationGestures וכו') |
| תמיכה בגרסאות | iOS 13 ומעלה | iOS 26 ומעלה בלבד |
| ניהול ניווט | WKNavigationDelegate עם שיטות delegate מרובות | פרוטוקול NavigationDeciding ואירועי ניווט |
| עקומת למידה | גבוהה — צריך להכיר UIKit, Delegates, KVO | נמוכה — דפוסי SwiftUI סטנדרטיים |
מדריך מיגרציה
יש לכם פרויקט קיים עם UIViewRepresentable? המעבר פשוט יחסית. הנה הצעדים:
- מחקו את מבנה ה-UIViewRepresentable כולו — כן, את הכל.
makeUIView,updateUIView,Coordinator— הכל הולך. - צרו
@State private var page = WebPage()— זה מחליף את כל הלוגיקה של KVO ו-Bindings. - השתמשו ב-
WebView(page)— במקום ה-View הישן. - העבירו את לוגיקת ה-Delegate — אם השתמשתם ב-
WKNavigationDelegate, מיירו את ההתנהגות באמצעותNavigationDeciding. - החליפו
evaluateJavaScript— ל-page.callJavaScriptעםasync/await. - מחקו את קוד ה-KVO — המאפיינים Observable של
WebPageמחליפים את כל ה-observeValue. לא תתגעגעו אליו.
תמיכה בגרסאות ישנות של iOS
ה-WebView המקורי דורש iOS 26 כגרסת מינימום. אם האפליקציה שלכם צריכה לתמוך גם בגרסאות ישנות (ורוב האפליקציות בעולם האמיתי צריכות), פשוט השתמשו ב-#available:
import SwiftUI
import WebKit
struct CrossVersionWebView: View {
let url: URL
var body: some View {
if #available(iOS 26, *) {
WebView(url: url)
} else {
LegacyWebView(url: url)
}
}
}
// Fallback for older iOS versions
struct LegacyWebView: UIViewRepresentable {
let url: URL
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.load(URLRequest(url: url))
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {}
}
גישה זו מנצלת את ה-API החדש אוטומטית על iOS 26 ונשארת תואמת לגרסאות ישנות. לפרויקטים חדשים שמכוונים ל-iOS 26 בלבד? אין צורך בנפילה הזו כלל.
אם אתם צריכים גם את הפונקציונליות המתקדמת (כותרת, התקדמות, JavaScript) בגרסאות ישנות, תצטרכו לשמור על שני מימושים מקבילים — WebPage עבור iOS 26 ו-UIViewRepresentable עם Coordinator עבור גרסאות ישנות. טיפ: עטפו את שניהם בפרוטוקול משותף כדי לשמור על ממשק אחיד.
שיטות עבודה מומלצות ומגבלות ידועות
שיטות עבודה מומלצות
- הגדירו תצורה בזמן האתחול — אובייקט
WebPage.Configurationחייב להיות מוגדר לפני יצירתWebPage. שינוי תצורה אחרי היצירה פשוט לא ישפיע. - טענו כתובות HTTPS בלבד — כתובות HTTP ייחסמו כברירת מחדל בגלל App Transport Security. חסכו לעצמכם דיבאגינג מיותר.
- טפלו בשגיאות ניווט — תמיד תוסיפו טיפול בכשל טעינה, אובדן חיבור או כתובת לא תקינה. המשתמשים שלכם יודו לכם.
- השתמשו ב-arguments עבור JavaScript — כשמריצים JavaScript עם קלט מהמשתמש, העבירו ערכים דרך הפרמטר
arguments:ולא דרך שרשור מחרוזות. זה מונע פגיעויות XSS. - הימנעו מלוגיקה כבדה ב-View body — אל תבצעו פעולות טעינה או JavaScript ישירות ב-
body. השתמשו ב-onAppear,taskאוButtonactions. - נהלו את מחזור החיים — זכרו ש-
WebPageשומר על מצב. רוצים לאפס? צרו מופע חדש.
מגבלות ידועות
- כפתורי ניווט לא מובנים — בניגוד ל-
SFSafariViewController, אין כפתורי חזרה, קדימה ורענון מובנים. תצטרכו ליצור אותם בעצמכם עםbackForwardList. - בעיות בעדכון תפריטי היסטוריה — יש באג ידוע שבו תפריטי
Menuשל ההיסטוריה לא תמיד מתעדכנים. הפתרון: שימוש ב-idדינמי על התפריט. - ניווט אחורה בעייתי לפעמים — מפתחים מדווחים שניווט אחורה לא תמיד עובד כצפוי עם אתרים שמשתמשים ב-JavaScript כבד. פתרון עוקף:
callJavaScript("history.back()"). - ביצועים ב-Layout — שילוב
WebViewבתוךScrollViewאו Layouts מורכבים עלול ליצור בעיות גודל ומיקום. תנו ל-WebViewמקום מלא עםignoresSafeAreaאו גבולות ברורים. - גרסת בטא — נכון לעכשיו, ה-API עדיין בגרסת בטא כחלק מ-iOS 26. ייתכנו שינויים עד לגרסה הסופית.
שאלות נפוצות
האם WebView ב-SwiftUI מחליף את WKWebView לגמרי?
לא בדיוק. WebView המקורי של SwiftUI הוא הדרך המומלצת להצגת תוכן אינטרנט באפליקציות SwiftUI חדשות, אבל WKWebView עדיין קיים וזמין. אם צריכים לתמוך בגרסאות ישנות מ-iOS 26, תצטרכו להמשיך עם UIViewRepresentable עבור אותן גרסאות. בנוסף, אם אתם צריכים גישה ל-API-ים מתקדמים של WKWebView שלא חשופים דרך WebPage, הגישה הישנה עדיין רלוונטית.
איך מונעים מה-WebView לנווט לאתרים חיצוניים?
משתמשים בפרוטוקול WebPage.NavigationDeciding. יוצרים מבנה שמציית לפרוטוקול, בודקים את ה-URL בכל בקשת ניווט, ומחזירים .allow או .cancel בהתאם. רוצים לפתוח כתובות חיצוניות בספארי? פשוט קראו ל-UIApplication.shared.open(url) לפני שמחזירים .cancel.
האם ניתן להשתמש ב-WebView עם SwiftUI ב-macOS?
כן. WebView זמין גם ב-macOS 26 ובפלטפורמות נוספות של Apple. ב-macOS, חלק מה-Modifiers מתנהגים קצת אחרת — למשל, webViewMagnificationGestures מגיב לתנועות trackpad במקום מחוות מגע. יש גם Modifiers נוספים ב-macOS כמו webViewScrollPosition לשליטה מתקדמת בגלילה.
איך מתמודדים עם שגיאות טעינה ב-WebView?
עוקבים אחרי אירועי ניווט באמצעות currentNavigationEvent של WebPage. כשמתקבל אירוע .failed, הוא כולל את אובייקט השגיאה שמאפשר לזהות את סוג הבעיה — אין חיבור, timeout, או כתובת לא תקינה. אפשר להציג הודעת שגיאה מותאמת או לנסות לטעון שוב. בנוסף, אפשר לבדוק את page.isLoading ו-page.url לזיהוי מצבים בעייתיים.
האם WebView תומך בקבצי cookies ושמירת סשן?
כן. WebPage בנוי מעל WebKit ומשתמש באותו מנגנון אחסון של WKWebView — כולל cookies, Local Storage ו-Session Storage. לשליטה מתקדמת יותר (הזרקה או מחיקה של cookies ספציפיים), ייתכן שתצטרכו API-ים ברמה נמוכה יותר. לניהול סשנים, שקלו להגדיר את ההתנהגות דרך WebPage.Configuration בזמן האתחול.