iOS 17 App 程式設計實戰心法(SwiftUI)

第 22 章
使用 WKWebView 與 SFSafariViewController 顯示網頁內容

I've got a theory that if you give 100% all of the time, somehow things will work out in the end.

- Larry Bird

需要在 App 中顯示網頁內容是很常見的事情,iOS SDK 提供了三種選項來讓開發者顯示網頁內容:

  • Mobile Safari - iOS SDK提供 API,可以讓你在內建的 Mobile Safari 瀏覽器中開啟特 定的 URL。在這種情況下,你的使用者會暫時離開應用程式,並切換至 Safari 來檢視網頁內容。
  • WKWebView - 這個視圖可讓開發者直接在 App中嵌入網頁內容。你可以將 WKWebView 視為專為應用程式整合而設計的精簡版的Safari,它負責載入一個 URL 請求,並顯示網頁內容。WKWebView 利用 Nitro JavaScript 引擎,並提供其他的功能。如果你的目標是顯示一個特定的網頁,WKWebView 是此案例的推薦選項。
  • SFSafariViewController - 雖然 WKWebView可以讓你嵌入網頁內容,但它並不能提供 開箱即用的完整網頁瀏覽體驗。例如:WKWebView 缺少「Back / Forward」按鈕來讓使用者瀏覽歷史紀錄。為了提供這個功能,開發者需要使用WKWebView 建立自訂的網頁瀏覽器。然而,隨著 SFSafariViewController 的導入,開發者不再需要從頭開始建立自己的網頁瀏覽器。透過利用 SFSafariViewController 使用者無須離開你的 App,即可享受 Mobile Safari 的所有功能,它在你的 App 中提供無縫的瀏覽體驗,包含導覽控制。
圖 22.1. FoodPin App 的 About 畫面
圖 22.1. FoodPin App 的 About 畫面

在本章中,我將引導你完成所有的選項,並向你展示如何使用它們來顯示網頁內容。對於 WKWebView 與SFSafariViewController,我們需要使用 UIViewRepresentable 與 UIView ControllerRepresentable 來結合這些元件,因為它們只在 UIKit 中可用。

為了示範如何在 SwiftUI 中顯示網頁內容,我們將建立About 標籤來顯示三種選項,如圖 22.1 所示:

  • Rate us on App Store - :選取後,我們會在 Mobile Safari中 載入一個特定的 iTunes 連結,使用者會離開目前的 App,並切換至 App Store。
  • Tell us your feedback - w:選取後,我們會使用 WKWebView 載入 「Contact Us」網頁。
  • Twitter / Facebook / Instagram :每一個項目都有其相對應的社群描述檔的連結,我們 會使用SFSafariViewController 來載入這些連結。

聽起來很有趣,對吧?我們開始吧 !

設計 About 視圖

首先,將本章所準備的圖片包( http://www.appcoda.com/resources/swift53/abouticons.zip ))匯入 Assets.xcasset,如圖 22.2 所示。

圖 22.2. 匯入圖片與圖示
圖 22.2. 匯入圖片與圖示

接下來,我們為 About 視圖建立單獨的檔案,在專案導覽器中的「View」資料夾上按右鍵,並選擇「New File...」,然後選取「SwiftUI View」模板,將檔案命名為「AboutView. swift」。

建立後,更新 AboutView 結構如下:

struct AboutView: View {
    var body: some View {
        NavigationStack {
            List {
                Image("about")
                    .resizable()
                    .scaledToFit()

                Section {
                    Label("Rate us on App Store", image: "store")

                    Label("Tell us your feedback", image: "chat")
                }

                Section {
                    Label("Twitter", image: "twitter")

                    Label("Facebook", image: "facebook")

                    Label("Instagram", image: "instagram")
                }
            }
            .listStyle(.grouped)

            .navigationTitle("About")
            .navigationBarTitleDisplayMode(.automatic)
        }
    }
}

我們利用一個 List 視圖來顯示可用的選項。SwiftUI 中的 List 視圖內建了對區塊的支援。在上列的程式碼中,我們建立了兩個區塊,一個區塊用來顯示使用者意見回饋的按鈕,另一個區塊用來顯示社群資訊。透過將清單樣式設定為「.grouped」,SwiftUI 會自動以灰色空白列來分隔各個區塊,從而增強清單的視覺組織及清晰度。

要顯示導覽列標題,則我們將 List 視圖嵌入到導覽視圖中,圖 22.3 顯示了帶有區塊的 List 視圖的預覽。

圖 22.3. About 視圖的佈局
圖 22.3. About 視圖的佈局

準備連結

當使用者點擊其中一個選項時,App 將會開啟對應的網頁連結。我們將使用列舉來儲存選項的連結,在 AboutView 中宣告以下的列舉:

enum WebLink: String {
    case rateUs = "https://www.apple.com/ios/app-store"
    case feedback = "https://www.appcoda.com/contact"
    case twitter = "https://www.twitter.com/appcodamobile"
    case facebook = "https://www.facebook.com/appcodamobile"
    case instagram = "https://www.instagram.com/appcodadotcom"
}

使用連結開啟 Safari

正如我在本章開頭所說,我將展示三種不同顯示網頁的方式。對於「Rate us on App Store」選項,App 會將使用者導引到內建的Safari 瀏覽器來顯示URL,SwiftUI 有一個名為「Link」的原生視圖元件用於此目的,你只需像下列程式碼這樣將「Rate us on App Store」的Label 包裹進去,即可啟用網頁連結:

Link(destination: URL(string: WebLink.rateUs.rawValue)!, label: {
    Label("Rate us on App Store", image: "store")
        .foregroundStyle(.primary)
})

destination 參數接受目標 URL 的 URL 物件,這裡我們將儲存在 WebLink 列舉中的 rateUS URL 傳送給它。我們新增 foregroundStyle 修飾器至 Label,以將文字顏色更改為黑色。預設上,如果你不進行任何更改,則所有的網頁連結都會顯示為藍色。

你不能在預覽窗格中測試該 Link 功能,不過為了在模擬器中測試它,我們需要修改 MainView.swift 檔案。在 MainView 中,將 Text("About") 替換為 AboutView(),如下所示:

AboutView()
    .tabItem {
        Label("About", systemImage: "square.stack")
    }
    .tag(2)

我們現在使用 AboutView,而不是顯示文字視圖。在模擬器中執行這個 App,並切換到 About 標籤,當你點擊「Rate us on App Store」按鈕時,App 將會開啟 Safari 瀏覽器, 如圖 22.4 所示。

圖 22.4. 點擊「評價我們」選項,以在 Safari 中開啟連結
圖 22.4. 點擊「評價我們」選項,以在 Safari 中開啟連結

使用 WKWebView

現在你應該充分了解如何使用 Link 視圖,我們來探討如何將網頁視圖嵌入你的 App 中。對於「Tell us your feedback」及社群資訊來說,我們將專注在 App 中嵌入網頁瀏覽器,你可以使用 WKWebView 或 SFSafariViewController 這兩種方式來顯示網頁內容。在本小節中,我會示範如何使用 WKWebView 來實現,下一小節將介紹 SFSafariViewController 的用法。

同樣的, 這兩個元件在 SwiftUI 中不可用, 因此我們需要利用 UIKit 框架並使用 UIViewRepresentable 來建立自己的視圖。

在專案導覽器中的「View」資料夾上按右鍵,並選擇「New File...」,然後選取「Swift File」模板,將檔案命名為「WebView.swift」。檔案內容替換如下:

import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {

    var url: URL

    func makeUIView(context: Context) -> WKWebView {
        return WKWebView()
    }

    func updateUIView(_ webView: WKWebView, context: Context) {
        let request = URLRequest(url: url)
        webView.load(request)
    }
}

WKWebView 是 WebKit 框架的一部分,因此必須在程式碼開頭匯入它。WebView 結構遵循UIViewRepresentable 協定,並實作所需的方法,即在 makeUIView 方法中,我們回傳 WKWebView 物件,並在updateUIView 方法中,我們載入指定的URL。

現在已經在我們的 SwiftUI 專案中使用 WebView 了。切換到 AboutView.swift,並新增一個狀態變數來儲存目前的連結:

@State private var link: WebLink?

要開啟網頁視圖,則將 .sheet 修飾器加到 List 視圖:

.sheet(item: $link) { item in
    if let url = URL(string: item.rawValue) {
        WebView(url: url)
    }
}

sheet 修飾器監看 link 變數的變化,如果它設定為特定的 URL,我們會建立一個 WebView 的實例,並顯示網頁內容。

一旦你新增 sheet 修飾器,Xcode 就會顯示下列的錯誤:

Instance method 'sheet(item:onDismiss:content:)' requires that 'AboutView.WebLink' conform to 'Identifiable'

根據上列的敘述,WebLink 型別應該要遵循 Identifiable 協定。如官方文件中所述, Identifiable 協定用於為類別或值型別建立一致的識別,在這種情況下,sheet 修飾器要求我們提供遵循Identifiable 協定的項目,以正確識別及管理所呈現的工作表。

那麼,我們該如何修正這個錯誤呢?或者我們如何讓 WebLink 遵循 Identificable 協定?

採用該協定非常容易,你只需要在 WebLink 列舉中新增一個 id 屬性,如下所示:

enum WebLink: String, Identifiable {
    case rateUs = "https://www.apple.com/ios/app-store"
    case feedback = "https://www.appcoda.com/contact"
    case twitter = "https://www.twitter.com/appcodamobile"
    case facebook = "https://www.facebook.com/appcodamobile"
    case instagram = "https://www.instagram.com/appcodadotcom"

    var id: UUID {
        UUID()
    }
}

這裡我們利用通用唯一識別碼(UUID,Universally Unique Identifier )作為識別碼,以確保任何時候的唯一性。當你相應更新 WebLink 列舉後,Xcode 錯誤應該會消失。

最後,除了「Rate us on App Store」標籤以外,其餘標籤皆使用 .onTapGesture 修飾器更新:

Section {
    Link(destination: URL(string: WebLink.rateUs.rawValue)!, label: {
        Label("Rate us on App Store", image: "store")
            .foregroundStyle(.primary)
    })

    Label("Tell us your feedback", image: "chat")
        .onTapGesture {
            link = .feedback
        }
}

Section {
    Label("Twitter", image: "twitter")
        .onTapGesture {
            link = .twitter
        }

    Label("Facebook", image: "facebook")
        .onTapGesture {
            link = .facebook
        }

    Label("Instagram", image: "instagram")
        .onTapGesture {
            link = .instagram
        }
}

點擊任何標籤時,我們設定 link 變數為對應的 URL,然後在預覽窗格或模擬器中執行這個 App,當點擊任何標籤時,網頁視圖將顯示為模態視圖(Modal View ),如圖 22.5 所示。

圖 22.5. 使用網頁視圖開啟連結
圖 22.5. 使用網頁視圖開啟連結

使用 SFSafariViewController

之前我們使用 WKWebView 在 App 中顯示網頁內容,而在本小節中,我們將探討使用 SFSafariViewController 來嵌入網頁瀏覽器。如果你不熟悉 SFSafariViewController 的話, 它是一個類別,提供全功能的網頁瀏覽器體驗,類似於內建在 iOS 中。

與 WKWebview 類似,我們需要使用 UIViewRepresentable 為 SFSafariViewController 建立自訂的 SwfitUI 視圖。為此,在專案導覽器中的「View」資料夾上按右鍵,並建立一個新檔案,然後選取「Swift File」模板,將檔案命名為「SafariView」。更新檔案內容如下:

import SwiftUI
import SafariServices

struct SafariView: UIViewControllerRepresentable {

    var url: URL

    func makeUIViewController(context: Context) -> SFSafariViewController {
        return SFSafariViewController(url: url)
    }

    func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {

    }
}

這個 SafariView 接受 URL 物件,然後我們在 makeUIViewController 中使用該 URL 來實例化 SFSafariViewController,這就是我們將 SFSafariViewController 與 SwiftUI 專案整合的方式。

現在切換到 AboutView.swift,將 WebView 替換為 SafariView,如下所示:

.sheet(item: $link) { item in
    if let url = URL(string: item.rawValue) {
        SafariView(url: url)
    }
}

App 將使用 SafariView 取代 WebView 來顯示網頁內容。執行 App 來進行測試,如圖 22.6 所示,這個 Safari 視圖帶有「返回」與「往前」按鈕,這便是 SFSafariViewController 的強大之處。如果你需要在 App 中顯示全功能的瀏覽器,則你可以選擇 SFSafariView Controller。

圖 22.6. 使用 Safari 視圖來開啟連結
圖 22.6. 使用 Safari 視圖來開啟連結

本章小結

我們探討了三種顯示網頁內容的選項,你不必要在你的 App 中使用所有的這些選項, 因為我們已經將它們用於示範目的。

SFSafariViewController 類別提供一個在你的 App 中嵌入網頁瀏覽器的便利方式,如果你的 App 需要為使用者提供無縫且功能豐富的瀏覽體驗,與建立你自己的自訂網頁瀏覽器相比,使用 Safari 視圖控制器可以節省大量時間及精力。

然而,在某些情況下,你可能只需要一些基本的網頁視圖來顯示網頁內容,在這種場景下,WKWebView 可能是更合適的選擇。花一些時間研究及評估我們涵蓋的所有網路瀏覽選項,然後選擇最適合你的特定需求及請求的選項。

在本章所準備的範例檔中,有最後完整的 Xcode 專案可供你下載參考: http://www.appcoda.com/resources/swift59/swiftui-foodpin-webview.zip