「開発」カテゴリーアーカイブ

実録!アプリ申請リジェクト — その後

あまりだらだら続けてもしょうがないので、その後の経緯をざっくりまとめ、最後に対処方法を書いてみます。

その後の経緯:

 App Store Connectサイトでアプリのメタデータを更新できなかった件については、その後1か月くらい経過した時点でいつの間にか更新できるようになっていました。さすがに開発者皆が利用しているサイトなので、どこかの時点でAppleが対応せざるを得なくなったと推察します。

「あなたのID」は、いつどこで利用するのですか?の質問ですが、これに関しては画面遷移図を作成し、トップ画面からどのような経路で「あなたのID」がある画面にたどり着くか、また「あなたのID」部分を赤まるで囲って、画像を見た瞬間に誰でもわかる形にしたところOKになりました。驚くことに、その後再度「あなたのID」は、いつどこで利用するのですか?と同じ質問が来たりしてびっくりしましたが、再度同じ画像を提示して事なきを得ました。

「利用規約(EULA)」と「プライバシーポリシー」の件についても、画面遷移図を作成し、赤まるで囲って”ここにあるよ”と明示したら通りました。

その後も、具体的な内容は省きますが上記以外の質問を5,6問ほど次々にされました。それも一度にではなくこちらが回答を返す度に、モグラたたきのように目についたから今回はこの質問!というような形でリジェクトと共に質問を投げられました。この審査員の方の仕事のやり方には疑問を感じざるを得ません。

対処方法:

アプリ審査員は当たり外れがある気がします。過去5,6アプリほど申請してリジェクトを喰らうことは全くなかったのですが、今回は急にリジェクトの嵐でした。

肝心の対処方法ですが、私が今回当たった審査員の場合、相手をスマホをさわったことのないIT素人だと思ったらうまくいきました。

こちらで当然だと思うことは全く通じないので回答として直感的に理解できる画像を多用しました。

明らかに審査員の落ち度だと思ってもそれを指摘せず、無感情で淡々と、誰にでもわかりやすい表現(画像)で説明しました。

付け加えると、こちら(開発者)に寄り添うような姿勢はみじんもなかったので、まじめな方ほど感情的になってしまい審査がうまくいかない気がします。

感情を殺して、素人相手だと思って過剰すぎるほど分かり易い回答を淡々と返すのが最善かと思います。

以上です。アプリ開発者の方に少しでもこの経験がお役に立てば幸いです。

実録!アプリ申請リジェクト — 初回の3つのリジェクト理由つづき 

初回の申請では3つのリジェクト理由を言い渡されました。前回のとおりそれぞれの理由に対して回答を返しましたが、Appleからの回答は、回答にならない回答でした。

以下の赤字が、私の回答に対するAppleの回答です。

リジェクト理由その1:

「あなたのID」は、いつどこで利用するのですか?

リジェクト理由1への回答:

・ユーザーがアプリを再インストールしたときや、iPhoneやiPadを買い替えたときです。 
・トップメニューの「そのほか」を選択後、「別デバイスの変更」からこのIDを利用します。

→ この回答に対する、Appleの回答:

続けるにはより多くの情報が必要です。

リジェクト理由その2:

あなたのアプリは、Paid Applications 契約の Schedule 2, Section 3.8(b) に規定される自動更新サブスクリプションの条件をすべて満たしていないことに気づきました。

以下の必要な情報が見つかりませんでした。
・プライバシーポリシーへの機能的なリンク
・利用規約(EULA)への機能的なリンク

リジェクト理由2への回答:

・トップメニューの「そのほか」を選択した後で表示される画面で「利用規約(EULA)」と「プライバシーポリシー」を表示するようにしました。

→この回答に対する、Appleの回答:

続けるにはより多くの情報が必要です。

リジェクト理由その3:

アプリのメタデータに次の必須項目が見つかりませんでした。
・使用条件(EULA)への機能的なリンク

リジェクト理由3への回答:

この2週間、何回も App Store Connect の「App情報」「使用許諾契約」からアプリのメタデータを変更しようとしましたが、 以下のエラーメッセージが表示されて変更することが出来ませんでした。
「変更内容を保存できませんでした。しばらくしてからもう一度お試しください。」

→この回答に対する、Appleの回答:

アプリのメタデータに使用条件(EULA)への機能的なリンクが見つかりませんでした。

・・・この回答を見た時「あ、そういう対応か・・・」と思いました。

面倒ごとはゴメンだ。そっちですべてなんとかしろよ。なんらかの揺るがない証拠を出してこない限りこっちは何もしないよ。

という感じでしょうか。

リジェクト理由1と2に関しては、こちらが説明しているにもかかわらず情報を更に寄越せと言ってきました。たぶんアプリを何も見てくれてないか、日本語が読めない方なのかもしれません。

リジェクト理由3に関しては、初回の回答と全く変化がありません。システムの不具合調査は面倒なので知らん、という感じでしょうか。

次回へ続く・・・

実録!アプリ申請リジェクト — 初回の3つのリジェクト理由

初回の申請では3つのリジェクト理由を言い渡されました。その内の1つ目のリジェクト理由は、以下のような簡潔な一文でした。

リジェクト理由その1:

「あなたのID」は、いつどこで利用するのですか?

「あなたのID」とは、スマホを買い替えた時などデータ移行に必要となるIDです。

「あなたのID」を表示する画面には、注釈で「※利用デバイスを移行する際に必要となります。」としっかり記述してあります。また、そのすぐ下の「別デバイスのIDに変更」のリンクで利用するようになっています。

このリジェクト理由をみて私は思いました。たぶん日本語がわからない人が審査してるのかな、と・・・

リジェクト理由その2:

あなたのアプリは、Paid Applications 契約の Schedule 2, Section 3.8(b) に規定される自動更新サブスクリプションの条件をすべて満たしていないことに気づきました。

以下の必要な情報が見つかりませんでした。
・プライバシーポリシーへの機能的なリンク
・利用規約(EULA)への機能的なリンク

プライバシーポリシーに関しては作成済みで、こんな文章です。アプリ内課金を実装したアプリは、アプリ内からこの文章へのリンクを追加する必要があるようです。

利用規約(EULA)に関しては未作成だったため頑張って作成しました。Steam向けの利用規約生成ツールなどが出回っていたため、それを利用したりしました。

リジェクト理由その3:

アプリのメタデータに次の必須項目が見つかりませんでした。
・使用条件(EULA)への機能的なリンク

リジェクト理由3に関しては、App Store Connect の「App情報」「使用許諾契約」からアプリのメタデータにEULAを記述する必要があります。しかしいくら記述して更新しようとしても、 以下のエラーメッセージが表示されて変更出来ませんでした。

「変更内容を保存できませんでした。しばらくしてからもう一度お試しください。」

なにか不備があるのか?と思いましたが何度チェックしてやり直したり、時間を置いてみても(2週間近くも!)ダメでした。単にApp Store Connect のサイトのバグのようでした。(開発者の皆さんなら経験があるかと思いますが、開発者が利用するApp Store Connect のサイトは比較的バグが多いです・・・)

わたしは利用規約(EULA)を作成後、プライバシーポリシーと利用規約(EULA)をアプリ内からリンクして表示可能にし、Appleに対して以下のように回答すると共に新しいアプリを申請しました。

リジェクト理由1への回答:

・ユーザーがアプリを再インストールしたときや、iPhoneやiPadを買い替えたときです。
・トップメニューの「そのほか」を選択後、「別デバイスの変更」からこのIDを利用します。

リジェクト理由2への回答:

・トップメニューの「そのほか」を選択した後で表示される画面で「利用規約(EULA)」と「プライバシーポリシー」を表示するようにしました。

リジェクト理由3への回答:

この2週間、何回も App Store Connect の「App情報」「使用許諾契約」からアプリのメタデータを変更しようとしましたが、 以下のエラーメッセージが表示されて変更することが出来ませんでした。
「変更内容を保存できませんでした。しばらくしてからもう一度お試しください。
• 使用許諾契約の変更を保存できませんでした。しばらくしてからもう一度お試しください。」

この時はこれでアプリ修正に関しては全て完了し、リジェクト理由3に関してはAppleが App Store Connectサイトのバグを認めて対処してくれると信じていました・・・

次回へ続く・・・

実録!アプリ申請リジェクト — アプリ内課金編

先月末、アプリ内課金を組み込んだテキスト漫画がリリースされました。

実は、リジェクトを8回受け、リリースまでになんと半年もかかりました。


リジェクトのショックで精神的に作業を行う気になれず、対応の間が空いてしまったこともありますが・・・

 
    /\ ⌒ヽ ペタン
   / /⌒)ノ ペタン
  ∧∧\/ ((  ∧_∧
 (´ Д ) ^ ) ))(・∀・;)
 / ⌒ノ(⌒ヽ⊂⌒ヽ
(  ノ ) ̄ ̄(0_ )
 ))_)  (   ) (_(
    アップル    開

アプリ内課金(月額)の実装ということもあり、通常よりもアプリ審査が厳しかったのかもしれません。

しかし、審査が厳しい=アプリの品質が上がるということですので、開発者は大変かもしれませんが、一般ユーザにとっては嬉しいことだと思います。

ここでは、開発者の皆さんのアプリ申請がスムーズに行くことを願い、アップルのリジェクト理由を記述しようと思います。

~~\(゚-゚*)バサッ

次回へ続く・・・

StoreKit2でシンプルな課金処理 第7回

今回は購入情報の復元(リストア)処理を行います。

※裏作業でSwiftUIにより、UI上部に”購入情報の復元(リストア)”のリンクを追加しました。課金処理の範囲外になるので「SwiftUI」の記述は詳しく行いませんが、第4回で紹介したAppleのサンプルコードが参考になると思います。

もちろんエラー処理は必要になりますが、購入情報の復元(リストア)処理を行うコードは、基本的に以下の1行だけです!

let result = try? await AppStore.sync()

課金アイテムの取得、課金処理、に続いて今回も1行だけです!

このコードが実行されると、ログインしているAppleIDに紐づいた課金情報が復元されるはずです。ログインしていない場合、ログインを促されると思います。

復元後、UIが更新されるはずですが、サンプルコードにはUIを更新するコードが一見、見当たりません。

おそらく、以下の

updateListenerTask = listenForTransactions()

このコードにより、トランザクションリスナーが開始されているため、課金関連処理が行われるとこのリスナーに検知されてUIが自動で変わる仕組みだと思います。(もし違ったら、詳しい方、コメント欄で教えてください!)

心配な場合は、リストア後にUIを更新するコードを念のため書いておくと良いかと思います。


以上、第1回から今回までで、課金に必要な処理は一通り説明しました。少し前までは、課金処理はとても複雑・面倒で、手を出しにくかった開発者の方も多かったかと思います。特に個人開発者の場合、できれば余分な実装に手間をかけたく無いですよね。

今回、StoreKit2で手軽に課金処理を実装可能になりましたので、いままで手を出せなかった方もどんどん手を出していただければと思います!

(੭ ˃̣̣̥ ω˂̣̣̥)੭ु⁾⁾

StoreKit2でシンプルな課金処理 第6回

今回は課金アイテムの購入処理を行います。

※実は裏作業で、「SwiftUI」により課金用のUIを作り込みました。課金処理の範囲外になるので「SwiftUI」の記述は詳しく行いませんが、第4回で紹介したAppleのサンプルコードが参考になると思います。

価格が表記されたボタン押下時に課金処理コードを記述します。

もちろんエラー処理は必要になりますが、基本的に課金処理を行うコードは以下の1行だけです。

let result = try await item.purchase()

※”item” は前回取得した課金アイテムです。

このコードが実行されると、

上記の画像のように、課金アイテムの詳細表示と、購入を促すUIが自動で表示されます。

以上です!

(੭ ˃̣̣̥ ω˂̣̣̥)੭ु⁾⁾

あとは、購入済みアイテムの場合、価格ボタンを購入済みと分かる表示に切り替える処理などが必要ですが、この部分も第4回で紹介したAppleのサンプルコード(function isPurchasedの部分)が参考になると思います。

Appleのサンプルコードは一見コード量が多いですが、課金を行うStoreKit2の中核部分は本当にシンプルなコードです。

UIの部分は皆さん好みがあると思いますので、Appleのサンプルコードを参考に独自に実装していただければと思います。

次回は、”リストア”処理を行います!!

(੭ ˃̣̣̥ ω˂̣̣̥)੭ु⁾⁾

Javascript と PHP での iOS(iPad)判定

iOS13以降、HTTP_USER_AGENTだけではiPadとPCの区別がつかなくなりました。

これは、9.7インチ以上のiPadの場合にHTTP_USER_AGENTに”iPad”の文字列が含まれなくなり、PCのブラウザ(MacのSafari)からのアクセスとほぼ同じになるケースが発生したからです。

PHPの場合、以前は以下のようなコードでiOS判定が可能でしたが、現在は無理です。

function is_ios() {
    $ua = $_SERVER['HTTP_USER_AGENT'];
    // iOSと判定する文字リスト
    $ua_list = array('iPhone', 'iPad', 'iPod');
    foreach ($ua_list as $ua_smt) {
        if (strpos($ua, $ua_smt) !== false) {
           return true;
        }
    } return false;
}

Javascript ならば、HTTP_USER_AGENTに加えて ‘ontouchend’ なども利用する事で判別可能です。

const ua = window.navigator.userAgent.toLowerCase();
const isIOS = ua.indexOf("iphone") >= 0 
   || ua.indexOf("ipad") >= 0 
   || (ua.indexOf('macintosh') > -1 
       && 'ontouchend' in document) /* iPad */
   || ua.indexOf("ipod") >= 0;

PHP内で判定するには、JavascriptなどからPHPに判定済みの情報を渡すことが必要です。

2022年2月現在、Mac、Windows、iPadからのHTTP_USER_AGENTは以下となります。

[Mac の Safari] と [iPad(9.7インチ以上)の Safari] の違いがほぼ無いことが確認出来ます。

[Mac の Safari]
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15:

[Mac の Chrome]
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36

[Windows の Chrome]
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36

[11インチiPad Pro(第2世代) の Safari]
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15

[11インチiPad Pro(第2世代) の Chrome]
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) Mozilla/5.0 (iPad; CPU OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/97.0.4692.84 Mobile/15E148 Safari/604.1

[iPad mini(第6世代) の Safari]
Mozilla/5.0 (iPad; CPU OS 15_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Mobile/15E148 Safari/604.1

StoreKit2でシンプルな課金処理 第5回

今回は、課金アイテムの取得と表示を行ないます。

課金アイテムは第3回でApp Store Connect上に定義しましたが、これを利用しようとするとSandbox環境でテスト用アカウントを利用する必要があり面倒です。

そこで、第4回のサンプルでも利用されている、ローカルでテストできるようにする仕組み(StoreKit Testing)を用います。

Xcodeのプロジェクトから「New File …」「StoreKit Configuration File」を選択、ファイル名を決めて「Create」で課金アイテム用ファイルを作成します。

作成したファイルを選択後「+」を押して、「Add Auto-Renewable Subscription」 (定期購読型) を追加します。

サブスクリプショングループも、第3回での設定と同じにします。

その他の設定も第3回での指定と同じにします。

課金単位を ¥ にするためには、StoreKit の設定ファイルを選択した状態で、メニュー「Editor」から「Default Storefont」や「Default localization」を選択して「Japan(JPY)」や「Japanese」を選択してください。

あとは「StoreKit Testing」を有効にするために、第4回で行なった手順1〜4の設定を行ってください。

これで準備は完了です。

1行で課金アイテムを取得出来ます。

let items = try await Product.products(for: ["uka.apple.textmanga_favo_120_month", "uka.apple.textmanga_adoff_120_month"])

(広告オフのアイテムも追加したので、2つのアイテムを取得しています)

上の画像は、取得した配列から “displayName” を「SwiftUI※」で表示したものです。

※課金処理の範囲外になるので「SwiftUI」の記述は詳しく行いませんが、以下のようなコードで表示しました。

List(items) { item in
    Text(item.displayName)
}

表示部分は、第4回で紹介したAppleのサンプルコードをどんどんパクりましょう。

次回は課金アイテムの購入処理を行います!!

(੭ ˃̣̣̥ ω˂̣̣̥)੭ु⁾⁾

StoreKit2でシンプルな課金処理 第4回

課金処理の実装は、昨年実施された WWDC21 でStoreKit2紹介時にサンプルとして利用されたプロジェクトを参考に行います。

開発元のAppleが紹介しているサンプルなので、利用するコードとしては最高です。

このページのDownloadボタンからサンプルプロジェクトを取得してください。

ダウンロードして解凍後、SKDemo.xcodeprojを開いてプロジェクトを立ち上げます。

プロジェクト実行前に、このページの手順1〜4の設定を行わないと課金アイテムが表示されないので注意してください。

下記画像の赤部分です。

設定後、実行すれば、サンプルプロジェクトが正常に動作します。

次回からはこのプロジェクトを元に、自分のプロジェクトへ自動更新サブスクリプションの部分だけを移植、調整していくこととします。

(੭ ˃̣̣̥ ω˂̣̣̥)੭ु⁾⁾

第5回へ続きます。

StoreKit2でシンプルな課金処理 第3回

前回に引き続き、事前の設定作業を行なっていきます。


下記6箇所の設定を行います。

1.まずは、一番上に水色の背景色で表示されている「ローカライゼーションを追加」の文字をクリックします。プルダウンが表示されるので “日本語” を選択します。

「サブスクリプショングループ表示名」は、下記画像の赤丸部分の箇所で表示されます。ここでは “テキスト漫画サブスクリプション” としました。

「App名表示オプション」は、上記のサブスクリプショングループ表示名と共に表示される名前で、そのままでもOKです。右上の「保存」ボタン押下後、元の画面に戻ります。

2.次に「サブスクリプション期間」を設定します。1ヶ月ごとに¥120円としたいので、ここでは “1ヶ月” をプルダウンから選択しました。

3.「サブスクリプション価格」の右にある「+」をクリックし、価格を決定します。テキスト漫画は日本向けに売るので、通貨は “JPY – 日本 円”、価格は “¥120” を選択し、「次へ」をクリックします。

他の国の価格は、上記で選択した価格をベースに自動でセットされます。必要に応じて調整後「作成」をクリックします。

4.次は「ローカライゼーション」の右にある「+」をクリックします。プルダウンが表示されるので “日本語” を選択します。

「サブスクリプション表示名」はApp Store に表示される App 内課金の名前です。ここでは “お気に入り(¥120/月)” としました。

後日追記:サブスクリプション表示名に価格情報を含めてはいけません。リジェクト喰らいました・・・

説明欄には、このサブスクリプションで実現可能な機能を記述します。文字数制限があるため簡潔に記述します。

5.次に、これは任意ですが、アプリ内課金用のイメージを指定します。

画像は 1024 x 1024 で指定します。下のチェックボックスをオンにすると、アプリをインストールしていないユーザがアプリストアでこのアプリを見たときに、どんなことが課金で実現できるのか確認できます。

6.最後に、「審査に関する情報」を設定します。

アプリ内課金で実現可能な機能を、スクリーンショットと共に文章での説明を行います。審査で利用されます。

(あまりにしょぼい機能だとリジェクトを喰らうと思います。自動更新サブスクリプションは特にそうだと思います・・・)

上記画像のようにステータスが「送信準備完了」となればOKです!(広告非表示のアプリ内課金も追加しました)

ついでに、上記画像右上の「App 用共用シークレット」をクリックして、あとで利用する「App用共有シークレット」のコードを生成しておきましょう。


以上で、App Store Connectでの設定はほぼ終わりました!

次回は Xcode を利用しての実装作業に入ります!!

(੭ ˃̣̣̥ ω˂̣̣̥)੭ु⁾⁾

第4回へ続きます。