uka.apple のすべての投稿

実録!アプリ申請リジェクト — 初回の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_ )
 ))_)  (   ) (_(
    アップル    開

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

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

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

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

次回へ続く・・・

テキスト漫画 お気に入り機能の使い方

テキスト漫画アプリにお気に入り機能を追加※しました。

利用手順は以下の通りです。

※月額利用料金(最低限)がかかります。

まず、トップ画面から「そのほか」を選択します。

「App内課金」をタップします。

お気に入り機能を購入します。

購入できたら、画面下の「閉じる」をタップします。

トップ画面に戻ると、メニューに「4.お気に入り」が追加されているので、タップします。

初期状態ではお気に入りに1つも登録されておらず何も表示されません。メニュー1〜3で気に入ったテキスト漫画の「☆」をタップしましょう。

お気に入りに登録したテキスト漫画は「☆」が「★」に変化します。

「ペシ!ペシ!」を登録

メニュー4に戻ると、先ほど登録したテキスト漫画が表示されます。

これで、たくさんのテキスト漫画の中からあなただけのコレクションを作成できます!!

テキスト漫画アプリを気に入っていただけましたら是非ご利用ください。

テキスト漫画アプリ 機種変更時等に利用する移行コードについて

近日、テキスト漫画に「お気に入り」や「広告オフ」の機能追加を予定しています。

アップデート申請時にAppleより、機種変更時等に利用する移行コードについて、1つのコードだけでの認証ではダメで(今までテキスト漫画では「あなたのID」というコードだけで移行可能でした)、2つ以上のコードが必要との指摘を受けました。

よって、次回バージョンからは「あなたのID」に加えて「ニックネーム」も必要になるため予めご承知おきください。

テキスト漫画アプリへのお気に入り機能や広告オフ機能の追加について

現在、テキスト漫画アプリに気に入ったテキスト漫画を登録していつでも呼び出せる「お気に入り機能」や「広告オフ機能」を月額課金(最低限の価格)で利用可能にすべく、アップルにアプリのアップデートを申請しておりますが、

5、6回ほど

リジェクトを喰らっておりアップデートできておりません。

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

現在リジェクトされないよう鋭意アプリ改善中ですので、新機能を利用したいユーザ様はしばらくお待ちください・・・

     -ーー ,,_
   r'”      `ヽ,__
   \       ∩/ ̄ ̄ ヽつ
  ノ ̄\ /”ヽ/ ”   ノ   ヽi
 |  \_)\ .\    ・  ・ |\
 \ ~ )     \ .\_  ( _●_)\_つ
    ̄       \_つ

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回へ続きます。