🧠

iOS 18.4で利用可能になったApple Intelligence「スマートリプライ」機能をTimeTreeのアプリに実装しました

に公開

こんにちは、iOSアプリエンジニアのkoogawaです。
iOS 18.4から利用可能になったApple Intelligenceの新機能「スマートリプライ」を、TimeTreeアプリにも実装しました。バージョン14.3.3から利用できますので、ぜひご利用ください。

ダウンロードはこちらから

この記事では、TimeTreeのiOSアプリにスマートリプライ機能を実装した際の手順や注意点を詳しく紹介します。

スマートリプライとは

Apple Intelligenceの機能で、メールやメッセージの返信文章を提案してくれる機能です。
iOS 18.4よりUIKitにAPIが提供されたので、私たちのアプリにもスマートリプライ機能を実装することが可能になりました。

実装手順

基本的にはAdopting Smart Reply in your messaging or email appの通りですが、このままでは動かないので補足していきます。

会話のコンテキストを作成する

まずは会話データを用意します。YourMailEntry クラスは一例なので、適宜ご自身のアプリに置き換えてください。

let yourEntries: [YourMailEntry] = [
    YourMailEntry(text: "Yay! I'm looking forward to it!", sender: "Bob", recipient: "Apple", date: Date().addingTimeInterval(-180), yourEntryIdentifier: "1"),
    YourMailEntry(text: "Fort the most part, yes.", sender: "Bob", recipient: "Apple", date: Date().addingTimeInterval(-120), yourEntryIdentifier: "2"),
    YourMailEntry(text: "I'm a bit worried that it will rain...", sender: "Bob", recipient: "Apple", date: Date().addingTimeInterval(-100), yourEntryIdentifier: "3"),
    YourMailEntry(text: "Dont worry, we've got lots fun!", sender: "Apple", recipient: "Bob", date: Date().addingTimeInterval(-80), yourEntryIdentifier: "4")
]

メッセージ文、送信者、受信者などの情報が必要になりますが、詳細は下記ドキュメントを参照してください。

次に、会話データを元に UIMailConversationContext を作成します。

func mailConversationContext(for yourEntries: [YourMailEntry]) -> UIMailConversationContext {
    var context: UIMailConversationContext = UIMailConversationContext()
    
    var contextEntries: [UIMailConversationContext.MailEntry] = []
    for yourEntry in yourEntries {
        var conversationEntry = UIMailConversationContext.MailEntry()
        
        conversationEntry.text = yourEntry.text
        conversationEntry.senderIdentifier = yourEntry.sender
        conversationEntry.primaryRecipientIdentifiers = [yourEntry.recipient]
        conversationEntry.sentDate = yourEntry.date
        conversationEntry.entryIdentifier = yourEntry.yourEntryIdentifier
        conversationEntry.kind = .personal
        
        contextEntries.append(conversationEntry)
    }
    
    // このままだとエラーになるので、スレッドを識別できるidを適宜指定する
    context.threadIdentifier = yourThreadObject.identifier
    context.entries = contextEntries
    
    var senderName = PersonNameComponents()
    senderName.givenName = "Sender's name"
    
    var recipientName = PersonNameComponents()
    recipientName.givenName = "Recipient's name"
    
    // このままだとエラーになるので、 ["2othroer": "Apple"] のようにユーザー識別子と名前の辞書をセットする
    context.participantNameByIdentifier = [senderIdentifier:senderName, recipientIdentifier: recipientName]
    
    // このままだとエラーになるので、 ["2othroer"] のようにユーザー識別子をセットする
    // よくわかってませんが、送信者の識別子をセットしておきました
    context.selfIdentifiers = [senderIdentifier]
    
    // このままだとエラーになるので、 ["3otyhroer"] のようにユーザー識別子をセットする
    // よくわかってませんが、会話に参加しているメンバーの識別子をセットしておきました
    context.responsePrimaryRecipientIdentifiers = [recipientIdentifier]
    
    return context
}

↑はApple Developerサイトのサンプルですが、このままでは動かないので、コメントを入れています。

会話コンテキストをテキストビューまたはテキストフィールドに添付する

作成した会話コンテキストをUITextFieldまたはUITextViewconversationContextにセットします。

entryField.conversationContext = context

このあとにテキストフィールドをタップすると、ソフトウェアキーボードの上にスマートリプライが表示されます。(虹色のテキスト部分)

ここで気づいた方もいるかもしれませんが、スマートリプライはUIKitの機能であり、現時点ではSwiftUIには対応していないようです。SwiftUIでも使いたい場合は、UITextField をラップするなど、一工夫する必要がありそうです。

うまくスマートリプライが表示されない場合はシステムがスマートリプライの提案を生成するタイミングを理解するを参照してください。

会話の内容を最新の状態に保つ

会話コンテキストはキーボードセッションに関連付けられているため、フォーカスが入力フィールドから外れた場合(つまり、キーボードが閉じられた場合?)は、以前に作成した会話コンテキストを更新または再生成し、入力フィールドを更新されたコンテキストに設定する必要があります。

entryField.conversationContext = context
entryField.inputDelegate?.conversationContext(context, didChange: entryField)

長文の回答を生成する

メールやその他の長文メッセージングアプリ用の仕組みも用意されているようですが、まだ試せていません。ここではサンプルコードのみを載せておきます。

func textField(_:UITextField, insertInputSuggestion inputSuggestion: UIInputSuggestion) {
    guard let smartReplySuggestion = inputSuggestion as? UISmartReplySuggestion else {
        return
    }
    
    // Call your model with smartReplySuggestion.smartReply,
    // then assign the result to your entry field's text property.
    let entryFieldText = YourModel.response(from:smartReplySuggestion.smartReply)
    entryField.text = entryFieldText
}

まとめ

TimeTreeのiOSアプリにスマートリプライ機能を実装した手順と注意点について解説しました。スマートリプライの機能についてはまだ情報が少ないため、「ここが間違っている」「私たちはこのような方法で実装しています」などのフィードバックがあれば、ぜひコメント欄でお知らせください。

私は入社して2ヶ月ほど経ちますが、今回紹介したスマートリプライ機能の追加のように、自ら手を挙げれば、やりたいことにチャレンジできる文化がTimeTreeには根付いていると感じています。

そんなTimeTreeでは、ミッションに向かって一緒に挑戦してくれる仲間を探しています。TimeTreeで働くことに興味がある方はぜひ、採用ページをご覧ください!

TimeTree Tech Blog

Discussion