WebView ב-SwiftUI ל-iOS 26: המדריך המלא להטמעת תוכן אינטרנט

מדריך מעשי על WebView המקורי ב-SwiftUI עבור iOS 26. למדו להטמיע תוכן אינטרנט באפליקציות Swift עם WebView ו-WebPage, כולל ניווט, JavaScript, HTML מקומי, Modifiers והשוואה ל-UIViewRepresentable.

מבוא: למה 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? המעבר פשוט יחסית. הנה הצעדים:

  1. מחקו את מבנה ה-UIViewRepresentable כולו — כן, את הכל. makeUIView, updateUIView, Coordinator — הכל הולך.
  2. צרו @State private var page = WebPage() — זה מחליף את כל הלוגיקה של KVO ו-Bindings.
  3. השתמשו ב-WebView(page) — במקום ה-View הישן.
  4. העבירו את לוגיקת ה-Delegate — אם השתמשתם ב-WKNavigationDelegate, מיירו את ההתנהגות באמצעות NavigationDeciding.
  5. החליפו evaluateJavaScript — ל-page.callJavaScript עם async/await.
  6. מחקו את קוד ה-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 או Button actions.
  • נהלו את מחזור החיים — זכרו ש-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 בזמן האתחול.

אודות הכותב Editorial Team

Our team of expert writers and editors.