20210728のiOSに関する記事は5件です。

SKStoreReviewController利用のベストプラクティス

SKStoreReviewController利用のベストプラクティスについて、プログラム・デザインなどいくつかの視点から見つつ解説します。 Overview SKStoreReviewControllerはiOS10.3以降で利用可能になった、ユーザーへシステムプロンプトでレビュー依頼するためのクラスです。 (1Human Interface GuidelineのRatings and Reviewsのページで掲載されているシステムプロンプトの例) Scene Based Lifecycle以前はrequestReview(), 以後はrequestReview(in:)のclass funcを呼び出すと上記画像のようなプロンプトが表示されます。 おそらく、すでに世界中のさまざまなプロダクトで利用されているプロンプトだと思いますが、実はAppleによってレビュー依頼のベストプラクティスが記載されているページがあります。この記事では、実装・HIG・ドキュメントなどをいくつか合わせて読み解きながらベストプラクティスについて解説していきます。 ベストプラクティスを学ぶ HIGのRatings and Reviewsのページを読むと以下のようなプラクティスが紹介されています。簡単に翻訳すると以下のようになります。 評価を求めるのは、ユーザーがアプリに興味を持ってくれた後にしましょう。 ユーザーが一刻を争う作業やストレスの多い作業をしているときは、邪魔をしないようにしましょう しつこく尋ねないようにしましょう (2 Rating and Reviewsページの心に留めておくべき考慮事項として紹介されているプラクティス) Human Interface Guidelineはあくまでガイドラインのため、具体的にどうするか?という情報は記載されていませんが、開発者用のRequesting App Store Reviewsというサンプルコードを読むと 3Use best practices for prompting users to leave a review for your app in the App Store. という記述が先頭にあり、レビュー依頼についてベストプラクティスが存在している(少なくともAppleのチームは認識しているであろう)ことが分かります。 このページで紹介されるベストプラクティスをまとめると3点です。 アプリのバージョンをストレージ(サンプルではUserDefaults)に保存し、以前レビューを尋ねたのとバージョンでは再度レビューを尋ねないようにする。 ユーザーが重要なタスクを達成した回数をストレージに保存し、それがプロジェクトごとに定義した閾値を超えた場合だけレビューを尋ねるようにする。 1と2の条件をパスした場合でもユーザーが操作を継続中でないか確認し、少しディレイを掛けた後にユーザー操作の妨害になってしまう場合はリクエストを取りやめ、操作中でない場合のみリクエストする。 これらを論理コードで表現すると以下のようになります。 guard 重要なタスクの達成回数 >= 閾値 else { return // 早期リターン } guard 現在のアプリバージョン != 最後にユーザーにレビューを依頼した時のアプリバージョン else { return // 早期リターン } DispatchQueue.main.asyncAfter(deadline: .now + 2.0 //任意の秒数) { if !ユーザーが操作中 { SKStoreReviewRequest.requestReview(in: view.window!.windowScene) } } 先程のHIGの記述と見比べてもらうと、 達成回数と閾値での判定⇄「評価を求めるのは、ユーザーがアプリに興味を持ってくれた後にしましょう。」 各バージョンで1度だけ依頼可能な制約⇄「しつこく尋ねないようにしましょう」 ディレイ後に操作中でないことを確認⇄「ユーザーが一刻を争う作業やストレスの多い作業をしているときは、邪魔をしないようにしましょう」 というように、ガイドラインの内容を綺麗に実現しているのが分かります。 ベストプラクティスを実装する いくつか改善点はありますが、こちらに実現例を実装しました。あくまで参考の1例程度にお考えください。 AppleのサンプルではkCFBundleVersionKeyを使ってバンドルのバージョンを使っていますが、こちらのサンプルではMarketing Versionを利用してグローバルな計算プロパティからバージョンを取得できるように実装しています。 var currentAppVersion: String? { // Get the current app version. let infoDictionaryKey = "CFBundleShortVersionString" guard let currentVersion = Bundle.main.object(forInfoDictionaryKey: infoDictionaryKey) as? String else { fatalError("Expected to find a app version in the info dictionary") } return currentVersion } 処理を意味的にスムーズにするために、canRequestReviewとrequestReview(in:conditionAfterWait:)を分けて実現しました。 canRequestReviewではベストプラクティスの1と2に相当する判定を行なっています。加えて、重複してリクエストされるのを防ぐためにDispatchWorkItemを利用して以前のタスクがあればキャンセルを実行してから新規リクエストをスケジュールするようにしています。 var canRequestReview: Bool { print("Process completed \(processCompletedCount) time(s)") print("Current app version: \(currentAppVersion ?? "nil")") return processCompletedCount >= thretholdCountForReviewRequest && currentAppVersion != lastVersionPromptedForReview } // この設計は良くないですが、代案も特に思いついてないのでこのままにしています。何か良い案あればコメントしていただけると助かります。 func requestReview(in windowScene: UIWindowScene, conditionAfterWait condition: @escaping () -> Bool) { // Cancel previous request. requestReviewWorkItem?.cancel() // Make new request work. requestReviewWorkItem = DispatchWorkItem(block: { [weak self] in if condition() { SKStoreReviewController.requestReview(in: windowScene) self?.lastVersionPromptedForReview = self?.currentAppVersion } }) // Scedule the request. DispatchQueue.main.asyncAfter(deadline: .now() + waitTimeForReviewRequest, execute: requestReviewWorkItem!) } requestReview(in:conditionAfterWait:)のconditionAfterWaitには、ユーザーの行動を見て操作中かどうかの判定値を渡します。 let requestReviewManager = RequestReviewManager(currentAppVersion: currentAppVersion) if requestReviewManager.canRequestReview { requestReviewManager.requestReview(in: view.window!.windowScene!) { /* 例えば、クロージャで[weak self]をキャプチャして、 `return self?.navigationController?.topViewController == self` と確認する場合は「画面移動をしていないということは、ユーザーが操作をしていない」と判断するということになる。 UITextViewが表示されている画面など、インタラクティブ性が高い場合はこの条件だけでは不十分であるが 単純な画面の場合はこれくらいで大丈夫。この判定はプロジェクトの要件によって様々になる。 */ return true } } Wrap up この記事では、Appleがサンプルコードで記載したレビュー依頼のベストプラクティスについて紹介・解説しました。 サンプルコードでは、HIGのプラクティスと対応して ユーザーが重要なタスクを達成した回数が閾値を超えているか判定 各バージョンで1度だけ依頼するように判定 ディレイを掛け、ユーザーが操作中でないことを確認した後にレビューをリクエスト というベストプラクティスが実現されていました。そして、最後に実装の1例を紹介しました。 レビューをちょうど良いタイミングで依頼できればAppStoreで良いレビューが多くなり検索で表示される回数も多くなることが見込め、プロダクトにとっては良いことが多いので、この記事を読んで興味を持っていただけたのならプログラマー・マーケター・デザイナーの方々で一度実現の相談をしてみると良いのではないでしょうか。 Apple inc., AppRating_2x.png, System Rating and Review Prompts, Ratings and Reviews, https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/ratings-and-reviews/, viewed: 2021/07/28 ↩ Apple inc., Ratings and Reviews 3~5 Paragraph, Human Interface Guideline, https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/ratings-and-reviews/, viewed: 2021/07/28 ↩ Apple inc., Requesting App Store Reviews, Apple Developer, https://developer.apple.com/documentation/storekit/requesting_app_store_reviews, viewed: 2021/07/28) ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SKStoreReviewControllerのベストプラクティス

SKStoreReviewController利用のベストプラクティスについて、プログラム・デザインなどいくつかの視点から見つつ解説します。 Overview SKStoreReviewControllerはiOS10.3以降で利用可能になった、ユーザーへシステムプロンプトでレビュー依頼するためのクラスです。 (1Human Interface GuidelineのRatings and Reviewsのページで掲載されているシステムプロンプトの例) Scene Based Lifecycle以前はrequestReview(), 以後はrequestReview(in:)のclass funcを呼び出すと上記画像のようなプロンプトが表示されます。 おそらく、すでに世界中のさまざまなプロダクトで利用されているプロンプトだと思いますが、実はAppleによってレビュー依頼のベストプラクティスが記載されているページがあります。この記事では、実装・HIG・ドキュメントなどをいくつか合わせて読み解きながらベストプラクティスについて解説していきます。 ベストプラクティスを学ぶ HIGのRatings and Reviewsのページを読むと以下のようなプラクティスが紹介されています。簡単に翻訳すると以下のようになります。 評価を求めるのは、ユーザーがアプリに興味を持ってくれた後にしましょう。 ユーザーが一刻を争う作業やストレスの多い作業をしているときは、邪魔をしないようにしましょう しつこく尋ねないようにしましょう (2 Rating and Reviewsページの心に留めておくべき考慮事項として紹介されているプラクティス) Human Interface Guidelineはあくまでガイドラインのため、具体的にどうするか?という情報は記載されていませんが、開発者用のRequesting App Store Reviewsというサンプルコードを読むと 3Use best practices for prompting users to leave a review for your app in the App Store. という記述が先頭にあり、レビュー依頼についてベストプラクティスが存在している(少なくともAppleのチームは認識しているであろう)ことが分かります。 このページで紹介されるベストプラクティスをまとめると3点です。 アプリのバージョンをストレージ(サンプルではUserDefaults)に保存し、以前レビューを尋ねたのと同じバージョンでは再度レビューを尋ねないようにする。 ユーザーが重要なタスクを達成した回数をストレージに保存し、それがプロジェクトごとに定義した閾値を超えた場合だけレビューを尋ねるようにする。 1と2の条件をパスした場合でもユーザーが操作を継続中でないか確認し、少しディレイを掛けた後にユーザー操作の妨害になってしまう場合はリクエストを取りやめ、操作中でない場合のみリクエストする。 これらを論理コードで表現すると以下のようになります。 guard 重要なタスクの達成回数 >= 閾値 else { return // 早期リターン } guard 現在のアプリバージョン != 最後にユーザーにレビューを依頼した時のアプリバージョン else { return // 早期リターン } DispatchQueue.main.asyncAfter(deadline: .now + 2.0 //任意の秒数) { if !ユーザーが操作中 { SKStoreReview Controller.requestReview(in: view.window!.windowScene) } } 先程のHIGの記述と見比べてもらうと、 達成回数と閾値での判定⇄「評価を求めるのは、ユーザーがアプリに興味を持ってくれた後にしましょう。」 各バージョンで1度だけ依頼可能な制約⇄「しつこく尋ねないようにしましょう」 ディレイ後に操作中でないことを確認⇄「ユーザーが一刻を争う作業やストレスの多い作業をしているときは、邪魔をしないようにしましょう」 というように、ガイドラインの内容を綺麗に実現しているのが分かります。 HIGの「しつこく尋ねないようにしましょう」を詳しく見ていくと 4評価依頼の間隔は少なくとも1~2週間空け、ユーザーがアプリに対してさらにエンゲージメントを示した後にのみ、再度評価を依頼します。 という記述があるため、達成回数判定の閾値は「ユーザーが初回利用から1~2週間ぐらいで達成しそうなぐらい」に調整しておくと良さそうです。 加えて、各バージョンで1度だけレビュー依頼可能な設計にしておくと、2回目以降のレビュー依頼はアプリのリリース頻度に依存するようになります。1週間スプリントだと1週間ですし、1ヶ月スプリントだと1ヶ月になります。 1と2の条件を組み合わせて利用しており、閾値の定義が適切、かつスプリントのサイクルが1週間以上の場合は、「評価依頼の間隔は少なくとも1~2週間空け」を満たし続けることが可能になります。 ベストプラクティスを実装する いくつか改善点はありますが、こちらに実現例を実装しました。あくまで参考の1例程度にお考えください。 AppleのサンプルではkCFBundleVersionKeyを使ってバンドルのバージョンを使っていますが、こちらのサンプルではMarketing Versionを利用してグローバルな計算プロパティからバージョンを取得できるように実装しています。 var currentAppVersion: String? { // Get the current app version. let infoDictionaryKey = "CFBundleShortVersionString" guard let currentVersion = Bundle.main.object(forInfoDictionaryKey: infoDictionaryKey) as? String else { fatalError("Expected to find a app version in the info dictionary") } return currentVersion } 処理を意味的にスムーズにするために、canRequestReviewとrequestReview(in:conditionAfterWait:)を分けて実現しました。 canRequestReviewではベストプラクティスの1と2に相当する判定を行なっています。加えて、重複してリクエストされるのを防ぐためにDispatchWorkItemを利用して以前のタスクがあればキャンセルを実行してから新規リクエストをスケジュールするようにしています。 var canRequestReview: Bool { print("Process completed \(processCompletedCount) time(s)") print("Current app version: \(currentAppVersion ?? "nil")") return processCompletedCount >= thretholdCountForReviewRequest && currentAppVersion != lastVersionPromptedForReview } // この設計は良くないですが、代案も特に思いついてないのでこのままにしています。何か良い案あればコメントしていただけると助かります。 func requestReview(in windowScene: UIWindowScene, conditionAfterWait condition: @escaping () -> Bool) { // Cancel previous request. requestReviewWorkItem?.cancel() // Make new request work. requestReviewWorkItem = DispatchWorkItem(block: { [weak self] in if condition() { SKStoreReviewController.requestReview(in: windowScene) self?.lastVersionPromptedForReview = self?.currentAppVersion } }) // Scedule the request. DispatchQueue.main.asyncAfter(deadline: .now() + waitTimeForReviewRequest, execute: requestReviewWorkItem!) } requestReview(in:conditionAfterWait:)のconditionAfterWaitには、ユーザーの行動を見て操作中かどうかの判定値を渡します。 let requestReviewManager = RequestReviewManager(currentAppVersion: currentAppVersion) if requestReviewManager.canRequestReview { requestReviewManager.requestReview(in: view.window!.windowScene!) { /* 例えば、クロージャで[weak self]をキャプチャして、 `return self?.navigationController?.topViewController == self` と確認する場合は「画面移動をしていないということは、ユーザーが操作をしていない」と判断するということになる。 UITextViewが表示されている画面など、インタラクティブ性が高い場合はこの条件だけでは不十分であるが 単純な画面の場合はこれくらいで大丈夫。この判定はプロジェクトの要件によって様々になる。 */ return true } } Wrap up この記事では、Appleがサンプルコードで記載したレビュー依頼のベストプラクティスについて紹介・解説しました。 サンプルコードでは、HIGのプラクティスと対応して ユーザーが重要なタスクを達成した回数が閾値を超えているか判定 各バージョンで1度だけ依頼するように判定 ディレイを掛け、ユーザーが操作中でないことを確認した後にレビューをリクエスト というベストプラクティスが実現されていました。そして、最後に実装の1例を紹介しました。 レビューをちょうど良いタイミングで依頼できればAppStoreで良いレビューが多くなり検索で表示される回数も多くなることが見込め、プロダクトにとっては良いことが多いので、この記事を読んで興味を持っていただけたのならプログラマー・マーケター・デザイナーの方々で一度実現の相談をしてみると良いのではないでしょうか。 Apple inc., AppRating_2x.png, System Rating and Review Prompts, Ratings and Reviews, https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/ratings-and-reviews/, viewed: 2021/07/28 ↩ Apple inc., Ratings and Reviews 3~5 Paragraph, Human Interface Guideline, https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/ratings-and-reviews/, viewed: 2021/07/28 ↩ Apple inc., Requesting App Store Reviews, Apple Developer, https://developer.apple.com/documentation/storekit/requesting_app_store_reviews, viewed: 2021/07/28) ↩ Apple inc., Don’t be a pest, Ratings and Reviews, https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/ratings-and-reviews/, viewed: 2021/07/29 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift]SKStoreReviewController.requestReview()がiOS14から非推奨になった件について

SKStoreReviewController.requestReview() レビューをユーザーに促す場合に使われていたコード。 iOS14以降は非推奨となった。 SKStoreReviewController.requestReview() 一行で書けていたのに残念? 公式によると requestReview(in:)を使いなさいとのこと。 iOS14以降の書き方 if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene { SKStoreReviewController.requestReview(in: scene) } このように書けば良さそう! お知らせ 現在、iOS開発案件を業務委託で募集中です(副業)。TwitterDMご依頼をお待ちしています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

表示関連

全体 この記事は下の記事の一部です。 とりあえずボタンを押したら変更できるようにしてみる gapというDouble型の変数を宣言してあげることで実現しています。 最初宣言する時にvar gap = 0で宣言したら、なんか色々エラーが出てきて、 Doubleg型がいいというのでここで宣言する必要もなかったのですが、面倒だったのでここで宣言しています。changeDay関数のところでDouble型にキャストした方が良かったですかね? var gap: Double = 0にしてみたのですが、まだエラーが出てきて、 Left side of mutating operator isn't mutable: 'self' is immutable と出てきたのですが、どうもこれはswiftが値参照であることに由来しているみたいで、勝手に変更できなくなっているみたいです。色々なところのコードで見かけるような形で素直に@State var gap: Double = 0 と宣言したところエラーが出なくなりました。 これも要勉強です。 @Stateが何者かよく分かっていないのです。 ↓ 本来、構造体の中でプロパティの更新はできないが、@Stateをつけることで可能になるらしいです。 下の記事を参照しました。 Button関数については参考書 SwiftUI対応-たった2日でマスターできるiPhoneアプリ開発集中講座-Xcode-iOS-14対応 を参考にしていてそこでButton関数の引数の書き方が崩れていることを説明していたのでわかりずらいかと思いますが、もう少し時間ができたらそれを参考にして書きたいと思います。 アプリのオブジェクを一つの画面に表示 方法が Vstack {}で括る Hstack {}で括る Zstack {}で括る の3つです。それぞれ単語の意味が Vstack {}で括る Vertical:垂直 stack:積み重ねる Vstack → 縦に積み重ねる Hstack {}で括る Horizontal:水平 stack:積み重ねる Hstack → 横に積み重ねる Zstack {}で括る Z-axis:Z軸 stack:積み重ねる Zstack → 手前に積み重ねる となっています。ZのところだけAppleリファレンスでZを使って説明されてなかったのですが(Back-to-Frontと書いてありました)ZということはZ軸の話をしていると推測しています。 参考文献 SwiftUI対応-たった2日でマスターできるiPhoneアプリ開発集中講座-Xcode-iOS-14対応
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

日付関連

全体 この記事は下の記事の一部です 日時の取得 まず今日が何日かを取得する方法です。 var now :Date = Date() これでDateというstructのnow(この名前はなんでもいい)という変数を宣言して、そこに初期値としてDate()という今の日時を取得できる関数で今のDateの値を代入しています。 日時の表示 Date型をString型に変える処理をprintDayという関数を作って表示しています。 format(yyyyMMみたいな)を指定してString型に変更しているだけなので、printDayという名前はおかしいかなと思いましたが、他にいいものが思いつかなかったのでとりあえずこれでいきたいと思います。 この関数の引数はDate型とString型でそれぞれ Date: Stringに直したいdate String: format(yy.M.ddみたいな) を指定しています。 DateFormatter型でformatterという定数をDateFormatter()で宣言して このformatterの .calendarというプロパティをCalendar関数でgregorian(グレゴリオ暦のこと)にして .dateFormatというプロパティに引数のf = "M/dd"を代入して設定しています。 DateFormatter型の.Stringメソッドを使ってDate型をString型に変更していると思われるんだけど詳しくわからなかったので要勉強。from:のところがいまいちよくわからないです。 printDay func printDay(d: Date, f: String) -> String { let formatter :DateFormatter = DateFormatter() formatter.calendar = Calendar(identifier: .gregorian) formatter.dateFormat = f return formatter.string(from: d as Date) } 表示する日付の変更 Date型のaddingTimeIntervalメソッドを使いたいんですけど、TimeIntervalではDoubleを使って欲しいみたいなので、Doubleをあげています。秒数で管理しているので、n日変更したいときはn*24*60*60のようにしてあげます。 changeDay func changeDay(d :Date, i: Double) -> Date { let returnDate = d.addingTimeInterval(i*24*60*60) return returnDate } 参考文献 https://developer.apple.com/documentation/foundation/timeinterval https://www.ajinohiraki.info/entry/2018/09/17/012022 https://capibara1969.com/2100/ https://qiita.com/k-yamada-github/items/8b6411959579fd6cd995 ↑printDay関数はこの人のやつが最終形態に近いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む