🌏

iOS18で使えるTranslationAPIをUIKitで使ってみた

2024/08/05に公開

私は、主にiOS/Flutterの開発をしている takashico といいます。
今回は、WWDC24のセッションで紹介された Translation API について UIKitのアプリへの組み込み方について紹介できればと思います。

Translation API とは

Appleの翻訳アプリで使用されている、言語翻訳機能がAPIで提供されるようになります。
セッション内の例では、多言語で書かれてたレビューの一覧をTrasnlastion APIを使って一括翻訳を行うユースケースが紹介されていました。これが無料で各アプリ内で使えるのは魅力的ですよね。それだけでなく、この翻訳はデバイス上で行われるため、オフラインで実行できるのも良いですね。
ぜひ使ってみたいところですが、全ての環境で使用できるわけではありません。
このAPIはSwiftUIでのみ提供される予定のため、UIkitでは簡単に使用することはできません。

そこで今回は、UIKitベースの画面でも使用できるようにする方法について検討してみたので、紹介します。

https://developer.apple.com/jp/videos/play/wwdc2024/10117/

いざ実装

以下の環境で開発をしています。

Tool Version
macOS Sonoma 14.5(23F79)
Xcode 16.0.0 Beta 3
iOS iOS 18 Beta 3

完成画面

UIKitの画面にSwiftUIで作成した翻訳機能を持ったボタンを配置して実装していきます。

実装については以下で共有していますので、気になる方はぜひ覗いてみてください。
https://github.com/takashico/translation_api_for_uikit_demo

実装構成

ここでは、Translation APIをUIKit上から使用する一例を紹介します。先ほども説明した通り、Translation APIはSwiftUIでのみ使用することができます。そこで、下の図のような構成で実装を進めていきます。

SwiftUI側の実装

まずは、SwiftUI単体でボタン押下をトリガーに Translation APIを実行するViewを作成してみます。

TranslationButtonView.swift
struct TranslationButtonView: View {

   private let sourceText = "こんにちは"

    var body: some View {
        Button {
            triggerTranslation()
        } label: {
            Image(systemName: "translate")
        }
        .translationTask(configuration) { session in
            do {
                print(response.targetText)
            } catch (let error) {
                print(error.localizedDescription)
            }
        }
    }
}

private extension TranslationButtonView {
    func triggerTranslation() {
        if configuration == nil {
            configuration = .init(source: Locale.Language(identifier: "ja"),
                                  target: Locale.Language(identifier: "en"))
        } else {
            configuration?.invalidate()
        }
    }
}

続いて、以下の機能を実装します。

  • sourceTextを外部から受け取れるようにする
  • 翻訳済みのテキストをコールバックできるようにする

TranslationButtonView.swift
struct TranslationButtonView: View {
    
    final class DataSource: ObservableObject {
        @Published var sourceText = ""
        @Published fileprivate(set) var targetText = ""
    }
    
    init(dataSource: DataSource) {
        self.dataSource = dataSource
    }
    
    @State private var configuration: TranslationSession.Configuration?
    @ObservedObject private var dataSource: DataSource

    var body: some View {
        Button {
            triggerTranslation()
        } label: {
            Image(systemName: "translate")
        }
        .translationTask(configuration) { session in
            do {
                let response = try await session.translate(dataSource.sourceText)
                dataSource.targetText = response.targetText
            } catch (let error) {
                dataSource.targetText = error.localizedDescription
            }
        }
    }
}

private extension TranslationButtonView {
    func triggerTranslation() {
        if configuration == nil {
            configuration = .init(source: Locale.Language(identifier: "ja"),
                                  target: Locale.Language(identifier: "en"))
        } else {
            configuration?.invalidate()
        }
    }
}

これで、翻訳機能を担うSwiftUI側の実装は完了です。
続いて、この TranslationButtonView をUIKit側に繋ぎ込んでみましょう。

UIKit側の実装

先ほど実装した、TranslationButtonView を使って以下の手順で実装を進めていきます。

  1. NavigationItemにTranslationButtonViewを配置する
  2. TextFieldに入力されたテキストを TranslationButtonView.Input と同期する
  3. 翻訳済みテキストを受け取り、Viewへ反映する

では、それぞれ見ていきましょう。

UIHostingControllerを使用して、SwiftUIで作成したTranslationButtonViewをUIKitの画面に組み込みます。

private var translationButtonViewDataSource = TranslationButtonView.DataSource()

let translationButton = TranslationButtonView(dataSource: translationButtonViewDataSource)
let hostingController = UIHostingController(rootView: translationButton)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
hostingController.view.backgroundColor = .clear
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: hostingController.view)

UIHostingController
https://developer.apple.com/documentation/swiftui/uihostingcontroller

TextFieldに入力されたテキストを TranslationButtonView.Inputと同期する

func textFieldDidChangeSelection(_ textField: UITextField) {
    // 入力値の同期
    translationButtonViewDataSource.sourceText = textField.text ?? ""
}

翻訳済みテキストを受け取り、Viewへ反映する

translationButtonViewDataSource.$targetText
    .receive(on: RunLoop.main)
    .assign(to: \.translatedTextView.text, on: self)
    .store(in: &cancellableSet)

以上で、TranslationButtonViewの表示と入力値と出力値の処理ができましたので、正常に動作するか見てみましょう。

完成

ボタン押下をトリガーに翻訳処理が実行できています🎉

まとめ

今回の記事では、WWDC24で紹介されたTranslation APIをUIKitベースのアプリケーションで使用する方法について解説しました。Translation APIは本来SwiftUIでのみ使用可能ですが、UIKitとSwiftUIを組み合わせることで、UIKitベースのアプリでも翻訳機能を活用することができます。

次回は、Flutter (Add to App)で使ってみたらどうなるのか?について紹介できればと思います🙋

Discussion