【iOS 14】SKOverlay を使ってオススメ/関連アプリを紹介する UI をお手軽実装

## はじめに

<details><summary>時事雑談</summary>新しい iPhone の発売が遅れるというアナウンスもあって，
iOS 14 のリリースの仕方が気になっていましたが，
9/15 のイベントの中でかなり急な感じでリリース発表がありました。
(えっ明日!? Xcode 12 GM 出てたっけ?)
iOS 14 リリース日に合わせてアプリの配信を。と
考えていたデベロッパも多いはずで間に合うことを祈りたいですね・・・</details>

iOS 14 で別のアプリをユーザーに薦め，すぐに App Store でインストール可能にする **SKOverlay** が使えるようになりました。

実装自体とても簡単で，UIKit，SwiftUI 両方で使えます。
自分の作った別のアプリを紹介するという形で組み込んでみたので
実装していて気づいた点を含め紹介します。
全体の動きはこんな感じです。

![SKOverlay_00](https://user-images.githubusercontent.com/8732417/93294379-40088500-f825-11ea-9004-b4d57c67c548.gif)

## SKOverlay とは

![<iOS>-<iOS 14.0 and later>](https://img.shields.io/badge/iOS-iOS 14.0 and later-brightgreen)　![<Xcode>-<Xcode 12.0 and later>](https://img.shields.io/badge/Xcode-Xcode 12.0 and later-brightgreen)

Developer サイト[^1] には下記のように説明があります。

> By displaying an overlay, you can recommend another app to users and enable them to download it immediately.

オススメしたいアプリやコンテンツに関連するアプリなどを表示させ，
App Store へ遷移させることでユーザがすぐにアプリ情報にアクセス，
アプリインストールが可能になります。

今まで App Store へのリンクや UI を用意していたのが，
ほんの少しの実装で代わりになってくれます。
iOS 14 以上対応なので，iOS 13 以下は別途対応が必要です。
(私はあえて iOS 13 以下は何もしないことにしました。)

アプリのレビュー促進(`SKStoreReviewController`[^4])など
簡単な実装で実現できる便利で Good な機能あるので StoreKit いいですよね。

### SwiftUI

`StoreKit` を `import` します。
指定された条件が `true` の場合に表示されます。
例えばボタンタップで表示させたりができます。

```swift

import SwiftUI
import StoreKit // 追加

struct ContentView: View {
    // SKOverlayを表示するかの状態
    @State private var showSKOverlay = false

    var body: some View {
        Button("オススメアプリを表示") {
            self.showSKOverlay.toggle()  // 状態切替
        }
        .appStoreOverlay(isPresented: $showSKOverlay) {
            SKOverlay.AppConfiguration(appIdentifier: "1234567890", position: .bottom)
        }
    }
}
```

`appIdentifier` は App Store のアプリページのリンクの
末尾の `id` を除いた数字部分を文字列にしたもので，
`https://apps.apple.com/jp/app/id1234567890`
だったら **"1234567890"** です。

表示位置は 2パターンあり，`.bottom` と `.bottomRaised` です。
後者は少し上に上がって表示されます。TabBar とか ToolBar を意識しているのかな？

|`.bottom`|`.bottomRaised`|
|:--:|:--:|
|<img width="498" alt="スクリーンショット 2020-09-16 5 53 38" src="https://user-images.githubusercontent.com/8732417/93294616-fcfae180-f825-11ea-9785-f6187bc0b562.png">|<img width="498" alt="スクリーンショット 2020-09-16 5 54 04" src="https://user-images.githubusercontent.com/8732417/93294623-02582c00-f826-11ea-86d8-52a434718d1a.png">|

Run した場合ビルド自体は通るのですが，プレビューを見ようとしたらできないです。
いつか治るといいのですが・・・・(リリース版でもダメ🤔)

<img width="389" alt="スクリーンショット 2020-09-16 14 13 07" src="https://user-images.githubusercontent.com/8732417/93294892-c5d90000-f826-11ea-8ae1-b3e6b5d8d474.png">

### UIKit

私は UIKit の方で対応したので，
このような関数を用意してコールしてみました。
scene に対して present するあたりが特徴でしょうか。

```swift
import StoreKit // 追加

@available(iOS 14.0, *)
private func displayOverlay() {
    guard let scene = view.window?.windowScene else { return }
    let config = SKOverlay.AppConfiguration(appIdentifier: self.myAppInfo.appIdentifier, position: .bottom)
    let overlay = SKOverlay(configuration: config)
    overlay.present(in: scene)
}
```

表示位置は同じで，`.bottom` と `.bottomRaised` の2パターンです。

![SKOverlay_01](https://user-images.githubusercontent.com/8732417/93296000-4b5daf80-f829-11ea-8572-4c09fe33f383.gif)

ダークモードも対応しているので色の設定等の対応は不要です。

![SKOverlay_02](https://user-images.githubusercontent.com/8732417/93295918-181b2080-f829-11ea-840d-fd5b24e2a56f.gif)

OPEN のところはローカライズされています。(私が英語環境のため)
インストール済みだったら該当のアプリが開きます。

インストールされていない場合やボタン以外のビューをタップすると
App Store に遷移します。

## 実装していての気づき

### シミュレータでは確認できない

実装してみてシミュレータで動作確認しようとしたところ，
アプリ情報が表示されなくて少し時間を無駄にしました。
iOS 14 の実機で確認すればちゃんと表示されます。

![SKOverlay_simularor_bottom](https://user-images.githubusercontent.com/8732417/93295972-3b45d000-f829-11ea-8e63-879825a5a090.png)


### スクロール量の調整

表示は画面の下からコンテンツに被さる形となるため，
position を`.bottomRaised` にすることで事足りる場合はいいのですが，
コンテンツが隠れてしまうことが多いと思います。

`UIScrollView`(もちろん `UITableView` や `UICollectionView`も)を
使っている場合はスクロール量を少し調整してあげたら良いと思います。
使っていない場合は制約を調整すれば良さそうです。

私のアプリは `UIScrollView` を使っていたので
下記のように iOS 14 以上ではコンテンツサイズを調整させました。

```swift
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    if #available(iOS 14.0, *) {
        self.baseScrollView.contentSize.height += 100.0
        self.displayOverlay()
    }
}
```

![SKOverlay_03](https://user-images.githubusercontent.com/8732417/93296098-7a742100-f829-11ea-8937-630dbb891e24.gif)

一応表示は下にスワイプすると隠れてくれます。

![SKOverlay_04](https://user-images.githubusercontent.com/8732417/93296161-9aa3e000-f829-11ea-8bfc-13155e45b0b8.gif)

表示/非表示が完了するタイミング，ロードが失敗した際に呼ばれる，
Delegate メソッドが用意されているので，
レイアウトをしっかりしたい場合等はこちらで調整しても良さそうです(`SKOverlayDelegate`)。[^2]

例えば，表示されたらコンテンツのサイズ変更し，
非表示になったらアニメーションつけてサイズを戻してあげたりできますね。
お手軽感は薄れますがこっちの方が良さそうです。

```swift

// overlay.delegate = self

@available(iOS 14.0, *)
extension HogeViewController: SKOverlayDelegate {
    // SKOverlayの表示が終わったときに呼ばれる
    func storeOverlayDidFinishPresentation(_ overlay: SKOverlay, transitionContext: SKOverlay.TransitionContext) {
        self.baseScrollView.contentSize.height += 100.0
    }

    // SKOverlayが非表示になり始めたときに呼ばれる
    func storeOverlayWillStartDismissal(_ overlay: SKOverlay, transitionContext: SKOverlay.TransitionContext) {
        UIView.animate(withDuration: 1.0) {
            self.baseScrollView.contentSize.height -= 100.0
        }
    }
}
```

![SKOverlay_06](https://user-images.githubusercontent.com/8732417/93298864-00469b00-f82f-11ea-84dd-33e6ca4a0ea6.gif)


### 適切なタイミングで非表示に

よく仕様を確認せず表示されたことに満足していたところ，
画面遷移などによって表示させたい画面を離れても
表示されたままになってしまい，UX を損なうことがありました。

VC に対しての present ではなく，scene に対しての present のためです。

```swift
/// Attempts to present an app overlay in a `UIWindowScene`.
@available(iOS 14.0, *)
open func present(in scene: UIWindowScene)
```

そのため，非表示にする実装もしっかりしないといけないです。
画面を離れる際に非表示にするように対応しました。

```swift
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if #available(iOS 14.0, *) {
        self.dismissOverlay()
    }
}

@available(iOS 14.0, *)
private func dismissOverlay() {
    guard let scene = view.window?.windowScene else { return }
    SKOverlay.dismiss(in: scene)
}
```

![SKOverlay_05](https://user-images.githubusercontent.com/8732417/93296189-adb6b000-f829-11ea-89ca-ea5f1a5971ef.gif)

## おわりに

iOS 14 から使えるようになった SKOverlay について書きました。

お手軽に実装できるので，
もし関連アプリやオススメしたいアプリがあったらぜひ実装したいですね。

強調しすぎるのも良くないので適切な画面，タイミングで表示し，
同じく非表示にしてあげるのも大事だと思いました。

ご覧いただきありがとうございました🙇‍♂️

App Clip でも使えるらしいけどキャッチアップがまだ・・・[^3]

[^1]: https://developer.apple.com/documentation/storekit/skoverlay
[^2]: https://developer.apple.com/documentation/storekit/skoverlaydelegate
[^3]: https://developer.apple.com/documentation/app_clips/recommending_an_app_clip_s_corresponding_app
[^4]: https://developer.apple.com/documentation/storekit/skstorereviewcontroller