SwiftUIでレビュー依頼ダイアログを出す


# はじめに

アプリのレビューはSKStoreReviewControllerを使うと、OSの体裁に沿ったレビュー依頼ダイアログを表示してくれます。
そもそもアプリのレビュー依頼はこの方法以外でやってはいけないようです。

実際表示されるのはこんな感じのダイアログです。

![Simulator Screen Shot - iPhone 8 - 2020-10-12 at 22.58.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200963/b20201d9-2281-0fcd-26f7-bad602c5a990.png "Simulator Screen Shot - iPhone 8 - 2020-10-12 at 22.58.36.png")

# どうやってSwiftUIで使うの？

Controllerという名前の通り、UIKitのクラスなのでUIViewControllerRepresentableでラップしてやる必要があります。
ほぼ[公式サンプル](https://developer.apple.com/documentation/storekit/skstorereviewcontroller/requesting_app_store_reviews)の丸パクリですが、私はStoreReviewViewという名前のクラスでラップして使っています。

``` Swift
import Foundation
import UIKit
import SwiftUI
import StoreKit

struct StoreReviewView: UIViewControllerRepresentable {
    typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
    
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<StoreReviewView>) -> UIViewController {
        let controller = UIViewController()

        return controller
    }
    
    func checkAndShowReview() {
        // If the count has not yet been stored, this will return 0
        var count = UserDefaults.standard.integer(forKey: UserDefaults.processCompletedCountKey)
        count += 1
        UserDefaults.standard.set(count, forKey: UserDefaults.processCompletedCountKey)
        
        // Get the current bundle version for the app
        let infoDictionaryKey = kCFBundleVersionKey as String
        guard let currentVersion = Bundle.main.object(forInfoDictionaryKey: infoDictionaryKey) as? String
            else { fatalError("Expected to find a bundle version in the info dictionary") }
        
        let lastVersionPromptedForReview = UserDefaults.standard.string(forKey: UserDefaults.lastVersionPromptedForReviewKey)
        
        // Has the process been completed several times and the user has not already been prompted for this version?
        if count >= 4 && currentVersion != lastVersionPromptedForReview {
            let twoSecondsFromNow = DispatchTime.now() + 2.0
            DispatchQueue.main.asyncAfter(deadline: twoSecondsFromNow) {
                SKStoreReviewController.requestReview()
                UserDefaults.standard.set(currentVersion, forKey: UserDefaults.lastVersionPromptedForReviewKey)
            }
        }
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<StoreReviewView>) {
    }
}

extension UserDefaults {
    class var processCompletedCountKey: String {
        return "processCompletedCount"
    }
    
    class var lastVersionPromptedForReviewKey: String {
        return "lastVersionPromptedForReview"
    }
}
```

# 呼び出し方

ダイアログを表示したいViewのonAppearでcheckAndShowReview()を呼びます。
本当はUIKitで言うviewDidLoad()のタイミングで処理を行いたいところですが、SwiftUIなのでそれはできません。
苦肉の策ですが、onApperでPIDチェックを行うことで表示される度にカウントが増えることを防止します。

``` Swift 
import SwiftUI

struct MainView: View {
    @State private var pid : Int32 = 0
    
    var body: some View {
        VStack {
            Text("Hello")
        }.onAppear() {
            if(pid != ProcessInfo.processInfo.processIdentifier) {
                StoreReviewView().checkAndShowReview()
                pid = ProcessInfo.processInfo.processIdentifier
            }
        }
    }
}

struct MainView_Previews: PreviewProvider {
    static var previews: some View {
        MainView()
    }
}
```