【visionOS】システムUIにSharePlay開始ボタンを表示させる実装例2つ

visionOSにおいて、FaceTimeで通話している間はウインドウにSharePlayや画面共有のためのシステムUIが表示されます。

![システムUIの例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/6fcb953a-19e8-0e88-c4be-b9198a41ae99.png)

このシステムUIからSharePlayを開始することが出来ます。

![システムUIからSharePlayを開始するボタン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/fd88b7b4-bd59-2d44-472b-505044b6e011.png)

今回は、このシステムUIにSharePlay開始ボタンを表示させるための実装例を2つ紹介します。


前置き
==============================================
今回の土台となるAPIについて、恥ずかしながら私自身は深く理解していないので、APIの仕様や挙動についての細かな解説は省略します。「大まかな仕組み」と「大まかな実装の流れ」、「具体的な実装例2つ」を紹介します。


大まかな仕組み
==============================================
**アプリ使用中にSiriに「これを共有して」と頼んで知り合いに任意のアイテムを共有する**機能と同じ仕組みで「システムUIにSharePlay開始ボタン」を表示させることが出来ます。

> **Siriを使用して情報を連絡先と共有する**
> 画面上にある写真、Webページ、Apple MusicやApple Podcastsなどのコンテンツ、「マップ」の位置情報などの項目を、連絡先に登録されている人と共有できます。
> 例えば、写真ライブラリの写真を見ているときに、Siriを起動して「これをお母さんに送信」のように言って、その写真を含む新しいメッセージを作成します。
> 引用: https://support.apple.com/ja-jp/guide/iphone/ipha48873ed6/ios

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/42a9ad21-701b-4352-365e-c9963592f202.png" width="270">

大まかな仕組みとしては、「OSの高度な共有機能(SiriやAirDropなど)を用いた時に、OSは画面内のコンテンツを一通りスキャンする。アプリが表示しているコンテンツの中に共有可能なものが埋め込まれていた場合に、OSはそれをピックアップしてシームレスな共有体験を実現する。」といった感じです。

また、「visionOSのウインドウのシステムUIにSharePlay開始ボタンを表示させる」実装方法と、「iOS 17のAirDropスタイルでSharePlayを開始する」実装方法は全く同じです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/1589a623-61d4-77bb-3e9d-2f7b7ef5186e.png" width="500">


大まかな実装の流れ
==============================================


ステップ1: アクティビティを示すオブジェクトを用意する
---------------------------------------------------------------------------------
例えば
- registerGroupActivityメソッドでGroupActivityを登録したNSItemProviderオブジェクト
- GroupActivityTransferRepresentationプロトコルに準拠したTransferableオブジェクト


ステップ2: 現在表示されている画面のどこかにステップ1のオブジェクトを埋め込んだ状態を作る
---------------------------------------------------------------------------------
例えば
- 任意のViewControllerのactivityItemsConfigurationにステップ1のオブジェクトを紐付ける
- ShareLinkにステップ1のオブジェクトを紐付ける


具体的な実装例
==============================================
今回は、GroupActivitiesフレームワークを代表するApple純正サンプルコード「DrawTogether」を題材に実装例を2つ紹介します。

https://developer.apple.com/documentation/groupactivities/drawing_content_in_a_group_session

DrawTogetherは単一のアクティビティのみで構成されるシングルウインドウアプリです。iOS/iPadOSに対応しています。

![実装後のスクショ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/b7002fb8-6122-b681-c65a-8ab68ce25f6c.jpeg)


パターン1: ウインドウのrootViewControllerにアクティビティを紐付ける
---------------------------------------------------------------------------------
アプリ起動直後などの適当なタイミングで、ウインドウのrootViewControllerのactivityItemsConfigurationにアクティビティを紐付けたNSItemProviderを登録します。

```swift
import LinkPresentation
```

```swift
let itemProvider = NSItemProvider()
itemProvider.registerGroupActivity(DrawTogether())

let configuration = UIActivityItemsConfiguration(itemProviders: [itemProvider])
configuration.metadataProvider = { key in
    guard key == .linkPresentationMetadata else { return nil }
    let metadata = LPLinkMetadata()
    metadata.title = "Share DrawTogether"
    metadata.imageProvider = NSItemProvider(object: UIImage(systemName: "pencil")!)
    return metadata
}

UIApplication
    .shared
    .connectedScenes
    .compactMap { $0 as? UIWindowScene }
    .first?
    .windows
    .first?
    .rootViewController?
    .activityItemsConfiguration = configuration
```


パターン2: アクティビティを紐付けたShareLinkを表示する
---------------------------------------------------------------------------------
アクティビティを紐付けたTransferableオブジェクトを用意します。今回は体裁を整えるためにApp Store製品ページのURLも紐付けました。そして、そのオブジェクトでShareLinkを作成します。

SharePlay中はShareLinkが不要になります。また、ユーザーが誤ってアクティビティを上書きしてしまうリスクもあります。そのため、SharePlay中はShareLinkを非表示にしました。

```swift
struct MyShareItem: Transferable {
    static var transferRepresentation: some TransferRepresentation {
        GroupActivityTransferRepresentation { _ in
            DrawTogether()
        }
        
        ProxyRepresentation { _ in
            URL(string: "https://apps.apple.com/app/id??????????")!
        }
    }
}
```

```swift
if canvas.groupSession == nil {
    ShareLink(item: MyShareItem(),
              preview: SharePreview("Share DrawTogether"))
}
```

![ShareLink実装例のスクショ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/eace0f73-ffd4-5695-cbfe-55fd73061414.jpeg)


補足
---------------------------------------------------------------------------------
複数のアクティビティがある場合や、マルチウインドウアプリの場合は実装が複雑になります。それらの説明については今回は省略します。


動作の安定性について
==============================================
「システムUIにSharePlay開始ボタンを表示する機能」は結構不安定です。

自分が動作確認した範囲だと、RealityViewの3Dオブジェクトを操作したりしてるとボタンが消えることがあります。また、オーナメントにShareLinkを置いてもSharePlay開始ボタンが表示されません。

![セッションビデオ内でAppleの人が「アプリ内で独自のSharePlay開始ボタンを用意しなくていい」と言ってる](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/6faa4070-d633-90ed-8c13-cecc9d072a9f.png)

セッションビデオ内でAppleの人が「アプリ内で独自のSharePlay開始ボタンを用意しなくていい」と言っていますが、あまり信用せずに独自のSharePlay開始ボタンも一応実装した方が無難だと思います。


公式ドキュメント
==============================================
https://developer.apple.com/wwdc23/10087?time=741

*↑ 12:21よりシステムUIについての概要と実装方法を説明 ↑*

https://developer.apple.com/documentation/groupactivities/promoting-shareplay-activities-from-your-apps-ui

https://developer.apple.com/jp/news/?id=94dxn2w2

https://developer.apple.com/wwdc23/10239

*↑ iOS 17のAirDropスタイルSharePlayの紹介 ↑*

https://developer.apple.com/wwdc23/102?time=1578

*↑ 26:19よりiOS 17のAirDrop実装の紹介 ↑*


余談
==============================================
【Apple Vision Proを持っている方への相談】「空間的なSharePlay」アプリの動作確認や動画撮影等に協力してくれませんか？

実機が1台だけでは「空間的なSharePlay」を動かすことが出来ません。お互いにApple Vision Pro実機からペルソナでFaceTime通話している時だけ「空間的なSharePlay」を動かすことが出来ます。ただ、残念ながら私にはApple Vision Proを持っている知り合いがいないのです。もし協力してくださる方がいらっしゃれば、こちらのメールアドレス(waggle.slips.0t@icloud.com)に連絡ください！

==== 追記 2024/12/01 ====
12月現在も協力者募集中です！
==== 追記ここまで ====

![ペルソナの姿でお願いする私](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/2b93774c-5361-8ede-ef47-5b355ea50683.jpeg)


* * *

https://zenn.dev/huiygfutfgvjknj/articles/e570d43eb75042

https://qiita.com/mjnfhbuvwebwfiejcnw/items/498ad1878dd48ea53412

https://zenn.dev/huiygfutfgvjknj/articles/9990f9ee42a410

https://zenn.dev/huiygfutfgvjknj/articles/5665e31e97284a