精通 SwiftUI - iOS 17 版

第 39 章
利用 Live Text API 從圖片中擷取文本

去年,iOS 15 新增了一個非常有用的功能 ── Live Text。你可能也有聽過 OCR(光學字元辨識的縮寫)一詞,這是一個將文本圖像轉換為機器可讀的文本格式的過程;這就是 Live Text 所做的事。

Live Text 內置在相機 App 和相片 App 中。如果你還沒有嘗試過這個功能,現在就來打開相機 App 試試吧!當我們把電話的鏡頭對准文本圖像,右下角就會出現一個 Live Text 按鈕。點擊按鈕後,iOS 就會自動擷取文本。然後,你就可以將其複制,並粘貼到其他 App(例如備忘錄 App)中。

對於使用者來說,這是一個非常強大又方便的功能。而對於開發者來說,我們都會希望把這個 Live Text 功能加到自己的 App 中。在 iOS 16,Apple 發佈了 Live Text API,讓開發者可以在自己的 App 中加入 Live Text 功能。在這個章節中,讓我們一起來看看如何在 SwiftUI 中使用 Live Text API。

利用 DataScannerViewController 啟用 Live Text

在 WWDC 的 Capturing Machine-readable Codes and Text with VisionKit Session 中,Apple 工程師展示了以下圖片:

圖 39.1. AVfoundation 和 Vision 框架提供的 API
圖 39.1. AVfoundation 和 Vision 框架提供的 API

文本辨識 (Text Recognization) 不是 iOS 16 的新功能。在舊版本的 iOS 上,我們已經可以使用 AVFoundation 和 Vision 框架中的 API,來偵測和辨識文本。但是,實作起來十分複雜,尤其是對於那些剛接觸 iOS 開發的人來說。

在新版本的 iOS 中,以上所有的步驟都被簡化為 VisionKit 中的一個新類別 DataScannerViewController。我們在 App 內使用這個視圖控制器,就可以自動顯示有 Live Text 功能的相機 UI。

要使用這個類別,讓我們先匯入 VisionKit 框架,並檢查設備是否支援資料掃描器功能:

DataScannerViewController.isSupported

Live Text API 只支援 2018 年後推出、有 Neural Engine 的設備。另外,我們也要檢查 availability,來確保使用者批准 App 使用資料掃描器:

DataScannerViewController.isAvailable

通過兩項檢查後,我們就可以開始掃描了。讓我們看看以下範例程式碼,來啟用有 Live Text 功能的相機:

let dataScanner = DataScannerViewController(
                     recognizedDataTypes: [.text()],
                     qualityLevel: .balanced,
                     isHighlightingEnabled: true
                  )

present(dataScanner, animated: true) {
    try? dataScanner.startScanning()
}

我們只需要創建一個 DataScannerViewController 實例,並指定要辨識的資料型別即可。在範例中,我們要辨識的是文本,因此我們會把資料型別指定為 .text()。準備好實例之後,我們就可以調用 startScanning() 方法來開始掃描。

在 SwiftUI 使用 DataScannerViewController

DataScannerViewController 類別現時只支援 UIKit,因此在 SwiftUI 我們需要多做幾個步驟。我們需要採用 UIViewControllerRepresentable 協定,才可以在 SwiftUI 專案中使用這個類別。在這個情況下,讓我們創建一個新的 DataScanner結構:

import SwiftUI
import VisionKit

struct DataScanner: UIViewControllerRepresentable {
    .
  .    
  .
}

這個結構會接受兩個 binding 變數 (variable):一個用於觸發資料掃描,另一個則用來儲存掃描到的文本的 binding。

@Binding var startScanning: Bool
@Binding var scanText: String

讓我們實作以下方法,來採用 UIViewControllerRepresentable 協定:

func makeUIViewController(context: Context) -> DataScannerViewController {
    let controller = DataScannerViewController(
                        recognizedDataTypes: [.text()],
                        qualityLevel: .balanced,
                        isHighlightingEnabled: true
                    )

    return controller
}

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

    if startScanning {
        try? uiViewController.startScanning()
    } else {
        uiViewController.stopScanning()
    }
}

makeUIViewController 方法中,我們回傳了一個 DataScannerViewController的實例。在 updateUIViewController 中,我們會根據 startScanning 的數值來開始或停止掃描。

然後,我們要實作以下的 DataScannerViewControllerDelegate 方法,來擷取掃描到的文本。

func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) {
  .
  .
  .
}

當使用者點擊偵測到的文本時,就會調用這個方法,因此我們會這樣實作它:

class Coordinator: NSObject, DataScannerViewControllerDelegate {
    var parent: DataScanner

    init(_ parent: DataScanner) {
        self.parent = parent
    }

    func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) {
        switch item {
        case .text(let text):
            parent.scanText = text.transcript
        default: break
        }
    }

}

func makeCoordinator() -> Coordinator {
    Coordinator(self)
}

我們會檢查辨别到的物件,如果辨別到任何文本,就會把它儲存。最後,在 makeUIViewController 方法中插入這行程式碼來配置 delegate:

controller.delegate = context.coordinator

現在,控制器已經可以在 SwiftUI 視圖中使用了。

利用 DataScanner 擷取文本

舉個例子,我們會構建一個 UI 簡單的文本掃描器 App。在啟動 App 之後,它會自動顯示 Live Text 的相機視圖。當偵測到文本時,使用者可以點擊文本來擷取它。掃描到的文本會在顯示在螢幕的下半部分。

圖 39.2. Live text 示範
圖 39.2. Live text 示範

創建好一個標準的 SwiftUI 專案後,讓我們打開 ContentView.swiftVisionKit框架。

import VisionKit

然後,我們要宣告幾個變數,來控制資料掃描器和掃描到的文本的操作。

@State private var startScanning = false
@State private var scanText = ""

body 的部分,讓我們這樣更新程式碼:

VStack(spacing: 0) {
    DataScanner(startScanning: $startScanning, scanText: $scanText)
        .frame(height: 400)

    Text(scanText)
        .frame(minWidth: 0, maxWidth: .infinity, maxHeight: .infinity)
        .background(in: Rectangle())
        .backgroundStyle(Color(uiColor: .systemGray6))

}
.task {
    if DataScannerViewController.isSupported && DataScannerViewController.isAvailable {
        startScanning.toggle()
    }
}

我們在 App 啟動時就開始啟用資料掃描器,但在此之前,我們需要調用 DataScannerViewController.isSupportedDataScannerViewController.isAvailable,來確保設備支援 Live Text。

這個範例 App 差不多完成了。因為 Live Text 需要相機的訪問權限,所以我們要到專案配置,在 Info.plist 檔案中添加 Privacy – Camera Usage DescriptionKey,並說明 App 需要訪問相機的原因。

圖 39.3. 添加 key 以啟用相機訪問
圖 39.3. 添加 key 以啟用相機訪問

完成後,我們可以在真實的 iOS 設備上測試 App 的 Live Text 功能。

Live Text 除了支援英語外,還支援法語、意大利語、德語、西班牙語、中文、葡萄牙語、日語、和韓語。

圖 39.4. App 示範
圖 39.4. App 示範

總結

很高興看到 Apple 向 iOS 開發人員開放實時文本功能。 雖然新的DataScannerViewController不是專門為 SwiftUI 設計的,但我們可以輕鬆地將其移植到我們的 Swift 項目中,並將實時文本功能整合到我們自己的應用程序中。 如果你打算發布下一個App更新,請考慮加入這一個功能,它肯定能提升用戶體驗。

在本章所準備的範例檔中,有最後完整的 Xcode 專案,可供你下載參考: