前回はPayPal にビジネスアカウントを作成する手順を紹介した。今回は、PayPalのSandBox(検証環境)を用いて決済テストを行う手順をご紹介する。ここでは次の手順で説明する。
SandBoxとはいわゆる検証環境・テスト環境のことで、一般的には、本番のシステム・サービスと同等の設定や操作ができ、動作を検証・テストする時に使う環境のことである。
PayPal SandBoxは、PayPalにビジネスアカウントを登録すると使用できるようになる。
PayPalの場合、SandBoxでのアカウントや決済カード情報などは、本番(実際に売買を行うサービス)と異なるダミーデータを用いる。また、決済ボタン・リンクやAPIといった決済機能もSandBoxで検証用に作成・適用して動作を確認する。
つまり、SandBoxを用いた動作確認は大きく次のような流れとなる。
・SandBox環境でアカウントを準備
・SandBox環境で決済機能を準備
・SandBox環境で決済を実行
・SandBox環境で決済がなされたことを確認
SandBoxでの販売や支払いは、自分のPayPalアカウントを用いるのではなく、検証用のダミーアカウントを用いる。早速、Developerダッシュボード へアクセスし、ここでは自分自身のビジネスアカウントでログインする。
画面左側メニューの「Testing Tools」から「Sandbox Accounts」へ進む。ここで、SandBox環境で用いる、商品を販売する側と購入する側の各アカウントを準備していく。

筆者の場合、(SandBoxではなく、本番のPayPalで)ビジネスアカウントを作成して2~3日後には、この画面のように自動的に2つのSanBox用アカウント(ビジネスアカウントとパーソナルアカウントがひとつずつ)が作成されていた。
SanBox用アカウントは追加もできるが、今回はデフォルトで作成されていたアカウントを利用し、販売する側はビジネスアカウント、購入する側はパーソナルアカウントでテストを行うことにする。
| SandBox環境での役割 | 動作確認する時のアカウント |
|---|---|
| 商品を販売するアカウント | デフォルトのビジネスアカウント |
| 商品を購入するアカウント | デフォルトのパーソナルアカウント |
まずは、デフォルトで作成されていたアカウントのパスワードを変更しておくことをお勧めする。各アカウント行の右側(下の画面の矢印部分)から「View/Edit account」より、パスワードなどアカウント情報を変更できる。パスワードに加え、アカウントの氏名やカード情報など、テストしたい内容に変更しておくと良い。

商品を販売する側と購入する側の各SandBoxアカウントが準備できたら、SandBox上で扱う商品やAPI連携といった、決済機能を準備していく。
商品を販売するSandBoxアカウント(*本番のPayPalアカウントではありません)で、SandBox環境(https://sandbox.paypal.com/mep/dashboard)へログインする。
ログイン後の画面は、本番のダッシュボード画面と同じであり、操作も本番サービスと同じであるため操作手順は割愛するが、筆者は支払いリンクを作成した。
SandBox環境で支払いリンクを作成したので、そのリンクに対する支払いを実行する。
商品を購入する側のSandBoxアカウント(*本番のPayPalアカウントではありません)で、SandBox環境(https://sandbox.paypal.com/mep/dashboard)へログインする。
もしくは、支払いリンクへアクセスした後、手順に従いログインしても良い。
決済操作は本番サービスと同じであるため操作手順は割愛する。
SandBox環境を利用することで、本番サービスで決済をせずとも、(SandBoxで実施した決済の)支払い通知を確認することができる。
SandBoxアカウントを準備した時と同様、Developerダッシュボード へ自分自身のビジネスアカウントでログインする。
各アカウント行の右側(下の画面の矢印部分)から「View notifications」より、通知内容を確認できる。

以上、今回は、PayPal SandBoxの使い方を簡単にご紹介した。
SandBoxの活用が、顧客との決済トラブル防止の一助になるのではと考えている。
それでは みなさん良い1日を。
]]>
PayPal にビジネスアカウントを作成する手順を紹介する。ここでは次の手順で説明する。
今回 海外顧客を視野に入れ、海外で利用ユーザが多いと言われているPayPalを導入することにした。PayPalビジネスアカウントの作成では審査があり、一旦審査に落ちると追加書類など手間がかかると予想される。
AIに「審査に落ちたらどうしよう」と相談したところ「PayPalの審査が通らなければ他の決済サービスを使えば良いよ」とのこと。そのアドバイスが幸いにも役に立つこともなく、筆者の場合は以下手順でビジネスアカウントを作成することができた。
結論から言うと、筆者が入力・提出した情報は次の通りである。
*筆者は個人事業主として登録したので、法人は内容が異なる可能性がある。
・姓、名、生年月日、国籍、職業
・電話番号、メールアドレス、パスワード
・自宅住所:政府発行の身分証明書の記載と一致していること、私書箱は不可
・事業の拠点となっている国
・事業:「個人事業主」と「ビジネス」から選択
・事業者名、商号・屋号:確定申告に使用する事業者名
・事業が登記された国または地域
・販売、またはサービスの内容:選択肢より指定する
・このアカウントの目的:選択肢より指定する
・マイナンバーカードの撮影と、自分自身の顔を撮影
以下に、それぞれ画面で示しながら説明していく。
では実際の登録手順をご紹介する。
まずは、PayPal(https://www.paypal.com/jp/home)へアクセスし、画面にある「新規登録」ボタンをクリックする。次に、「パーソナル」か「事業用」か選択する画面では「事業用」を選択し先へ進む。
「ビジネスアカウントを開設する」画面となるので、情報を入力していく。項目は上から、姓、名、Country、電話番号、メールアドレス、パスワードである。
メールアドレスに関する余談だが、筆者は過去に、別件でGメールアカウントがロックされ復旧に苦労したため、ここではGメールではないアドレスを利用した。

画面下にある「同意してアカウントを作成」ボタンを押下すると、先ほど入力したメールアドレスに認証コードが届く。それを画面に入力しアカウント作成が完了する。
続いて以下の画面で「開始」ボタンを押下するとアカウントの設定へ進む。

プロフィールの作成画面が表示され、事業を「個人事業主」か「ビジネス」から選択する。筆者は「個人事業主」を選択した。

ここからは、より詳細な事業情報を入力していく。項目は、上から、姓、名、生年月日、国籍、職業、自宅住所(身分証明書と一致すること、私書箱は不可)である。

続いて、事業についての質問に答えていく。必須項目は上から、事業者名、商号・屋号、事業が登記された国または地域、販売またはサービスする内容、アカウントの目的、事業用ウェブサイトの有無、利用明細に記載する氏名、住所である。
ここを過ぎれば本人確認をして終わるため、頑張って進んでいきたい。

筆者は開業届けやウェブサイトは未対応だったため、商号・屋号などには予定している名称を入力した。
また、「販売、またはサービスの内容についてお知らせください」項目は選択肢が多く、迷いながらも一番近いと思うものを指定した。
そして最後、本人確認へと進む。まずは確認方法に同意する画面が表示されるため、「同意して続行」をクリックする。

本人確認用の情報を入力する。項目は上から、姓、名、生年月日、職業、PayPalを利用する目的、住所、提示する証明書(運転免許証やマイナンバーカードから選択する)である。筆者はマイナンバーカードを選択した。

ここで「続行」をクリックするとQRコードが表示される。コードをスマホで読み込み、指示に従いマイナンバーカードと自分の顔を撮影する。
ここまでで、情報の入力・送信作業が完了する。
画面に、PayPal側の確認が済んだらメールが届く(最長10日かかることがある)と表示されたので、しばし待つことにする。
ビジネスアカウントの登録作業をしてから、筆者の場合は数時間で「本人確認が完了しました」のメールが届き、無事にビジネスアカウントの作成が完了した。
このあと、売上を振り込む先の銀行口座登録などを実施した。
事前のAI相談では、この項目ではこう書いた方がいいとか、こういう内容だと審査に落ちやすいとか、かなり困難な作業という印象だった。しかし、実際は記入項目は簡単なものが多く、本人確認がマイナンバーということもあってか、予想以上にすんなり登録することができた。
ビジネスアカウントができたら、商品用の支払い機能(ボタンやリンク、API連携など)を作成し運用していくが、事前に支払い画面など動作を確認したいと思う。
PayPalはSandBox(検証環境)を提供しているため、次回はこれを用いたテストをご紹介する。
以上です。みなさん良い1日を。
]]>
Localツールとは、ローカル環境(自分のPC)にWebサーバやDB、WordPress環境を構築できるツールで、以下からDLできる。| 筆者のマシン | |
|---|---|
| Mac | mac mini (M1, 2020) |
| OS | Sequoia バージョン 15.5 |
| Localツール | Version 9.2.4+6788 |
ここでは次の手順で説明する。
筆者の環境では、Mac mini に標準で搭載されている Apache httpd を稼働しており、使用ポートは80である。
% sudo lsof -i:80
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
httpd 8095 root 4u IPv6 0xe755010baa99781c 0t0 TCP *:http (LISTEN)
上記の状態でLocalツールをDL・インストールし、サイト(サイト名は「test050526」)を作成したところ、Localツール画面上部に以下の警告メッセージが表示された。

この状態でLocalツール上の「Open site」ボタンを押下しサイトを表示すると、WordPressサイトトップページは表示されず、以下のように「It works! Apple httpd」が表示される。尚、この時のURLは「test050526.local」(サイト名.local)である。

ブラウザに表示された内容は、筆者の環境でMacの標準Apacheで稼働するWebサイトのトップページ(http://localhost:80)と同じ内容である。
更にLocalツール上の「WP Admin」ボタンを押下しWordPressの管理画面(管理者画面)を開くと、管理画面が表示されず以下のように「Not Found」となる。

本記事では、これらへ対応しLocalツールで作成したサイトのWordPressが正しく稼働・表示する手順を紹介する。
Localツール画面上部の警告メッセージにある「Learn more.」リンクをクリックする(以下の赤枠)。

Localツールのヘルプ「What is Router Mode?」ページ(以下URL)が表示される。
https://localwp.com/help-docs/advanced/router-mode/
ここを読むと、Localツールはポートに関連して以下2つのモードを使い分けることがわかる。(説明欄は翻訳ソフトにて翻訳)
| ルーターモード | 説明 |
|---|---|
| Site Domain Router Mode | Localが実行するデフォルトのモードです。このモードでは、Localはポート80をリッスンする「ルーター」を起動し、トラフィックをLocalが管理する個々のサイトに誘導します。各サイトのドメインは次のようになります。example.local |
| localhost Router Mode | このフォールバックモードでは、ローカルルーターを起動せず、代わりにコンピュータの専用ポートを使用して各サイトに直接アクセスします。このモードを使用すると、ドメインは次のようになります。localhost:10005 |
Localツールは、デフォルトでは Site Domain ルーターモードとなっており、それは80ポートをListen(使用)すると書いてある。
80ポートはすでに標準インストールのApacheで使用中であるため競合したと考える。
Localツールの画面を再度確認すると、以下のように確かに「Site Domain」という項目があり、値が「test050526.local」(サイト名.local)となっている。これは「Open Site」ボタンでブラウザを表示した時のURLである。

筆者は標準インストールされているApacheを80ポートで動かしたままLocalツールでサイトを動作させたい。
そのため、Site Domain ではなく、localhost ルーターモードを使用することにした。
localhostルーターモードへの変更手順はいくつかあるが、今回は以下手順で実施した。
Localツール上の「Open site」ボタンを押下しサイトを表示すると、Macの標準Apacheで稼働するWebサイトではなく、以下のようにWordPressのファーストページが正しく表示されるようになった。

ブラウザのアドレスは「localhost:10030」となっており、先ほどはアドレスが「test050526.local」であったため、ルータモードの切り替えによりドメインとポートが変わったことも分かる。
次にLocalツール上の「WP Admin」ボタンを押下しWordPressの管理画面を開くと、こちらも以下のように正しく管理画面が表示されるようになった。

以上のように、ルータモードをデフォルトの Site domain から localhostルーターモードへ変更することで、標準インストールのApacheを80ポートで稼働したまま、LocalツールでWordPressを動かすことができるようになった。
ちなみに、Localツールのlocalhostルーターモードは HTTPS サポート外である。
HTTPS接続を使いたい人は、標準インストールのApatchを停止してLocalツールを起動するか、もしくは標準インストールのApacheポートを80以外にするなどし、Site Domain ルーターモードを使うようにする必要がある。
以上
]]>
Mac mini に標準インストールされているApacheでPHPを使う手順を紹介する。| 筆者のマシン | |
|---|---|
| Mac | mac mini (M1, 2020) |
| OS | Sequoia バージョン 15.4.1 |
| Apache httpd | バージョン 2.4.62 |
| Homebrew | バージョン 4.4.20 |
ここでは次の手順で説明する。
PHPコードを用いてWebページを制作する場合、Apache と PHP を連携させれば良い。
しかし筆者の環境では、標準インストールされているApache の httpd.conf の記述に以下があり、PHPが非推奨となっている。
そこで以下サイトを参考にさせて頂き、brewでPHPをインストールした上で、標準インストールされているApacheでPHPを使用することにした。
・Apacheでphpモジュールがロードできない(macOS/Monterey)
・Add PHP to Apache on MacOS 12
% brew list
% brew install php
% brew info php
==> **php**: stable 8.4.6 (bottled), HEAD
・・・(省略)
To enable PHP in Apache add the following to httpd.conf and restart Apache:
LoadModule php_module /usr/local/opt/php/lib/httpd/modules/libphp.so
% brew services start php
==> **Successfully started `php` (label: homebrew.mxcl.php)**
brewでインストールしたPHPは署名されていないため、署名する。

| 名前 | hogeCA (自分で名前を付ける。あとで httpd.conf に記述するため英字を推奨する) |
| 固有名のタイプ | 自己署名ルートCA |
| ユーザ証明書 | コード署名 |
| メールの送信先 | 自分のメールアドレス |



% codesign --sign "hogeCA" --force --keychain /Users/me/Library/Keychains/login.keychain-db /usr/local/opt/php/lib/httpd/modules/libphp.so
LoadModule php_module /usr/local/opt/php/lib/httpd/modules/libphp.so "hogeCA”
<IfModule php_module>
AddType application/x-httpd-php .php
</IfModule>
% sudo apachectl configtest
% sudo apachectl -k restart
以下のように簡単なPHPコードを準備する。ここでは、ドキュメントルートは/Library/WebServer/Documentsである。
% pwd
/Library/WebServer/Documents
% cat phptest.php
<html>
<head></head>
<body>
<?php
echo 'This is php code.';
?>
</body>
</html>
ブラウザでhttp://localhost/phptest.phpへアクセスし「This is php code.」が表示されればOKである。
これでPHPによるWebページ制作ができるようになった。
% apachectl configtestコマンドにおいて、以下エラーが発生した時の対応をご紹介する。
httpd.conf の編集で、authority name「”hogeCA”」をコピペしたのだが、右側の「”」(RIGHT DOUBLE QUOTATION MARK)が悪かったようで、編集時にキーボードより直接「“」を打ち入力したところエラーは解消した。
補足であるが、PHPモジュールに対する署名情報は以下コマンドで確認できる
% codesign -dv --verbose=4 "/usr/local/opt/php/lib/httpd/modules/libphp.so"
% apachectl configtestコマンドにおいて、以下メッセージが発生した時の対応をご紹介する。
このメッセージはこのままでも状況によっては問題はないが、気持ち悪いので解消しておく。
httpd.conf に以下記述を設定(元々 ServerName行はコメントアウトされていた)。
以上、みなさん良い1日を。
]]>
WordPressを使っていると、時々編集画面が重くなったり、動作が不安定になったりすることがありますよね。その原因の一つとして、リビジョン(記事の過去の編集履歴)が蓄積しすぎてしまうことが挙げられます。リビジョンが多くなりすぎると、データベースが膨大になり、必要なメモリ量も増加することがあります。最悪の場合、メモリ不足やFatal Errorが発生する可能性もあります。
今回は、この問題を解決するために、リビジョン管理を制限して、編集画面を快適に保つ方法をお伝えします。
fatal error: allowed memory size of 67108864 bytes exhausted
fatal errorの例。できれば一生出会いたくない文章です。。
直接wp-config.phpファイルを編集してリビジョン数を制限する方法です。これは、WordPressが記事を保存する際に、リビジョンをどのくらい保持するかを設定する方法になります。
具体的には、以下のコードを wp-config.php に追加します。
define('WP_POST_REVISIONS', 5); // 最新5回のリビジョンのみ保存
wp-config.phpに上記のコードを記述すると、最大5つのリビジョンだけが保存され、古いリビジョンは自動的に削除されます。リビジョンの数は、ここでは5としていますが、状況に合わせて任意の数に調整できます。
また、以下のようにWP_POST_REVISIONSの値をfalseに設定すると、リビジョンが保存されなくなります。
define('WP_POST_REVISIONS', false); // リビジョンを保存しない
ただし、こちらの場合は編集履歴が全く保存されませんので、後から復元する事ができなくなります。
どちらも注意点としては、
/* That's all, stop editing! Happy publishing. */
または
/* 編集が必要なのはここまでです ! WordPress でのパブリッシングをお楽しみください。 */
というコメントアウトよりも上に記述するようにしてください。なぜかというと、このコメントの後の
require_once ABSPATH . 'wp-settings.php';
という記述よりも後ろに追加すると、リビジョン設定が反映されないからです。
リビジョンを管理するためのプラグインをインストールして、リビジョンの保存数を制限する方法があります。これにより、不要な履歴が増えすぎることを防ぎ、データベースを軽く保つことができます。
おすすめのプラグインには、"WP-Optimize" や "WP Revisions Control" などがあります。これらのプラグインは、記事ごとにリビジョンの保存数を設定できる機能を提供しており、例えば「5回までのリビジョンだけを保存」といった具合に、必要最低限の履歴だけを保持できます。
すでに蓄積されてしまった不要なリビジョンは、手動で削除することも可能です。これには、前述のWP-Optimize を使うと簡単に行えます。WP-Optimizeは、不要なリビジョンを一括で削除してデータベースをクリーンアップすることができます。
手動でリビジョンを削除する手順は以下の通りです。
なお、phpMyAdminなどでデータベースにアクセスしてSQLクエリを実行する、という方法もありますが、ここでは説明を省かせていただきます。
リビジョン管理をうまく行うことで、WordPressの編集画面を軽く保ち、動作不良を防ぐことができます。リビジョン数を制限する方法は、wp-config.phpで直接設定する方法、リビジョン管理系プラグインを使う方法などがありますので、自分の使いやすい方法で試してみてください。
もし、既にリビジョンが溜まりすぎている場合は、手動で削除することも忘れずに行いましょう。これで、快適にWordPressを使い続けることができるはずです。
]]>
画像の下にキャプションを入れる際、画像の幅と揃える小技です。
今回はfigureとfigcaptionタグで実装します。
サンプルページは下記URLよりどうぞ。
<figure>
<img src="dummy.gif">
<figcaption>
ダミーテキストダミーテキストダミーテキスト。ダミーテキストダミーテキストダミーテキスト。
</figcaption>
</figure>
必要部分だけの記述です。
全体をfigureで囲んでいます。
figure {
display: table;
}
figcaption {
display: table-caption;
caption-side: bottom;
}
figureに対してdisplay:tableとします。
これによってfigureの幅が要素の幅と同じになります。
次にfigcaptionに対してdisplay:table-captionとします。
これでfigureと同じ幅でキャプションが表示されるようになります。ただ、このままだとキャプションが画像の上に配置されるので、caption-side:bottomを加えキャプションが画像の下にくるようにします。
また、figureを中央合わせにしたい場合には、figureに対してmargin-inline:autoを加える事で実現できます。
]]>
よくある質問やQ&Aのようなページで、カパカパ開閉するアコーディオンを実装する際に使われることが多いjQueryのsildeToggle。
jQueryを使用すればわずか数行で記述できるため非常に便利なのですが、昨今の脱jQueryの流れに乗り、今回はjQueryを使わずにCSS gridとJavaScriptで再現してみました。
サンプルページは下記URLよりどうぞ。
<dl class="grid_toggle">
<dt class="toggle_button">ここをクリックするとtoggleします</dt>
<dd class="toggle_contents">
<div class="toggle_contents_inner">
<p>ダミーテキストダミーテキストダミーテキスト。</p>
<p>ダミーテキストダミーテキストダミーテキスト。</p>
<p>ダミーテキストダミーテキストダミーテキスト。</p>
</div>
</dd>
</dl>
必要部分だけの記述です。
全体をgrid_toggleで囲み、toggle_buttonをクリックすることでtoggle_contentsがパカパカと開閉するようにします。
/* ベース */
.grid_toggle {
display: grid;
grid-template-rows: 60px 0fr;
transition: grid-template-rows .3s ease;
&.open {
grid-template-rows: 60px 1fr;
}
}
.toggle_button {
cursor: pointer;
}
.toggle_contents {
overflow-y: hidden;
}
/* 装飾部分のみ */
.grid_toggle {
width: 900px;
margin-block-start: 1em;
margin-inline: auto;
}
.toggle_button {
display: flex;
align-items: center;
background-color: #ebf0f5;
padding-inline: 2em 4em;
}
.toggle_contents_inner {
border: 1px solid #ddd;
border-top: unset;
padding: 1em;
}
grid-template-rowsがCSS transitionに対応していることを利用し、grid-template-rowsを0frとすることと、toggle_contentsをoverflow-y:hiddenとすることで非表示状態を再現しています。
さらにopenでgrid-template-rowsを0fr→1frとし、後述のJavaScriptでこのクラスを付与(toggle)してtoggle_contentsを開閉するようにしています。
document.addEventListener('DOMContentLoaded', function(){
const gridToggleButton = document.querySelectorAll('.toggle_button');
gridToggleButton.forEach(function(elem){
elem.addEventListener('click', function(){
elem.parentNode.classList.toggle('open');
})
})
})
toggle_buttonをクリックすることで親要素のgrid_toggleにopenをtoggleさせています。
]]>
iPhone などの iOSアプリにおいて、StoreKit 2を用いたリストア(復元)処理の検証に関して、筆者の体験を紹介する。
| 筆者のマシン | |
|---|---|
| Mac | mac mini (M1, 2020) |
| OS | Monterey バージョン 12.4 |
| XCode | バージョン 13.4.1 |
| Swift | バージョン 5.6.1 |
ここでは次の手順で説明する。
iOSアプリの App内課金処理を StoreKit 2 で実装してApp Store の審査に提出した際、リストア(復元)のボタンを設置していないと言う理由で、Review での却下(Reject)を受けた。
理由は「Guideline 3.1.1 – Business – Payments – In-App Purchase」で、メッセージを日本語に訳すと、大まかには次のとおり。
余談だが、下記をざっと読んだ感じでは、私にはその必要性は認識できなかった。
・In-App Purchase Programming Guide の “Restoring Purchase Products” section
筆者が審査に提出したアプリは、起動時に最新の購入状況を読み込むものであったが、Rejectメッセージには次のステップが明記されていた。
内容を日本語に訳すと、大まかには次のとおり。
つまり、起動時に自動的に最新の購入アイテムを復元していても、明示的な”Restore”ボタンなどを設置することが求められた。
確かに、Implementing a store in your app using the StoreKit APIからDLできるサンプルアプリには、購入したアイテムを復元するための明示的なボタン「Restore Puchases」がある。
rejectされたアプリにはアイテム購入画面があるのだが、その画面の下部の方に、「購入情報の復元(リストア)」と言うボタンを設けた。
そして、そのボタンがタップされたときの処理として、上記サンプルアプリのコードおよび AppStore.sync()のAPI を参考にして次の処理を追加した。
AppStore.sync()
// 加えて、この後ろに、最新の購入済みのアイテムを反映した画面を表示する処理を実装
動作確認のために、TestFlightへアプリをアップロードし、手元の2台の実機で動作の確認を行った。
流れは次のとおり。
ここで、APIの説明や先のサンプルアプリのコードを確認したが、実装に不足な点は見当たらなかった。
試しに、AppStore.sync()のエラーをcatchした際のログを画面に表示するようにして、TestFlightへアップして検証した。
上記3の「購入情報の復元(リストア)」ボタンをタップでは下記エラーが出ていることがわかった。
TestFlight版アプリではSandBoxユーザを用いて検証したが、ネットでは一部挙動が怪しいと言う記事もあったので、本番のApp Storeでは動くと言う前提で、やむを得ず審査に提出をした。
ちなみに、上記3の後、実機Bで購入(復元ではない)を操作すると、認証後に「すでに購入済みです。無料で入手できます」と言う旨のメッセージが出て、購入済みであることが反映された。
審査が通過したので、App Storeからリリース版をDLし、TestFlight版アプリで検証した上記操作を試した(この時は自分のApple IDで課金アイテムを購入)。
これらより、AppStore.sync()を呼び出すことにより、本番の App Store では適切に復元(リストア)できることが確認できた。
TestFlight検証時がたまたま良くなかったのか、SandBoxテストユーザではリストア検証できない仕様なのかは分からなかったが、
StoreKit2 でのリストア処理はAppStore.sync()で問題なし!と言うことが分かった。
以上
]]>
iPhone などの iOSアプリで並列処理を行う、簡単なサンプルコードを紹介する。
| 筆者のマシン | |
|---|---|
| Mac | mac mini (M1, 2020) |
| OS | Monterey バージョン 12.4 |
| XCode | バージョン 13.4.1 |
| Swift | バージョン 5.6.1 |
ここでは次の手順で説明する。
iOSアプリにおいて、複数の処理を並列に実行させたいときがある。例えば
といったタスクがあるとする。
タスクAの処理が完了した後にタスクBを処理させるのではなく、タスクAとタスクBを並行に処理させたいことがある。
タスクAとタスクBのどちらが先に完了しても問題がない場合は、並列処理をすることで、速やかな画面描画を実現することができる。
ここでは、タスクAとタスクBといった複数タスクを並列処理させる、簡単なサンプルプログラムを紹介する。
尚、本記事で紹介するコード全体(XCodeプロジェクト)は、GitHub で公開している。
(サンプルで用いる音源データはアップしていないため、コード実行時は別途ご準備ください)
ここでは、並列に実施する処理として、以下の2つのTaskを作成する。
今回は処理を簡単にするため、非同期処理としてよく扱われるURLへのリクエストとレスポンス受信といった処理ではなく、2秒待つといった処理としている。
Task1とTask2を同時に実行開始することで、2秒後にワンワンとニャンが同時に鳴き、更にその2秒後にワンワンだけが鳴くようになる。
まず、下記メソッドは2秒待つという処理である。
// This task is a pseudo asynchronous processing and actually sleeps for 2 seconds.
private func taskSleep(taskName: String) async throws {
print("\(date) \(taskName) Start")
try await Task.sleep(nanoseconds: 2_000_000_000)
print("\(date) \(taskName) End")
}
このサンプルでは、引数taskNameでタスク名を受け取り、2秒を待つ前後にログを出力している。
Task1は次の通りである。
// Dog Task
// after await task, a dog barks, again after await task, a dog barks.
Task {
try await taskSleep(taskName: "Dog-1")
animalSoundPlayer.playDog()
try await taskSleep(taskName: "Dog-2")
animalSoundPlayer.playDog()
}
4行目のtaskSleepメソッドでは、2秒待つと言う処理がなされる。
5行目のanimalSoundPlayer.playDog()は、ワンワンと鳴く自作コードである。
6行目のtaskSleepメソッドでは、更に2秒待つと言う処理がなされる。
7行目のanimalSoundPlayer.playDog()を呼び出すことで、再度ワンワンと鳴く。
Task2は次の通りである。
// Cat Task
// after await task, a cat mews
Task {
try await taskSleep(taskName: "Cat-1")
animalSoundPlayer.playCat()
}
4行目のtaskSleepメソッドでは、2秒待つと言う処理がなされる。
5行目のanimalSoundPlayer.playCat()は、ニャンと鳴く自作コードである。
今回のサンプルコードでは、上記の各Taskを iPhone画面上のボタン「Animal Sound Play」をタップした際に動作させる。
ContentView.swiftの全体コードは以下である。
struct ContentView: View {
private let animalSoundPlayer = AnimalSoundsPlayer()
// Current date
private var date: String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
return dateFormatter.string(from: Date())
}
var body: some View {
Button("Animal Sound Play", action: {
// Dog Task and Cat Task are parallel processes.
// Dog Task
// after await task, a dog barks, again after await task, a dog barks.
Task {
try await taskSleep(taskName: "Dog-1")
animalSoundPlayer.playDog()
try await taskSleep(taskName: "Dog-2")
animalSoundPlayer.playDog()
}
// Cat Task
// after await task, a cat mews
Task {
try await taskSleep(taskName: "Cat-1")
animalSoundPlayer.playCat()
}
})
}
// This task is a pseudo asynchronous processing and actually sleeps for 2 seconds.
private func taskSleep(taskName: String) async throws {
print("\(date) \(taskName) Start")
try await Task.sleep(nanoseconds: 2_000_000_000)
print("\(date) \(taskName) End")
}
}
上記ContentView.swiftコードを用いたアプリは、次のような画面となる。
画面上の「Animal Sound Play」をタップすることで、Task1とTask2が同時に開始される。
XCode の Simulator でアプリを起動し、画面をタップした時のログは以下の通りである。
2022/08/05 10:06:48 Dog-1 Start
2022/08/05 10:06:48 Cat-1 Start
2022/08/05 10:06:50 Cat-1 End
2022/08/05 10:06:50 Dog-1 End
2022/08/05 10:06:50 Dog-2 Start
2022/08/05 10:06:52 Dog-2 End
10:06:48 に、Task1のtaskSleepメソッド(taskNameはDog-1) と Task2のtaskSleepメソッド(taskNameはCat-1)が実行されており、
いずれも2秒後の10:06:50にメソッド処理が完了する。
この後ワンワンとニャンが同時に鳴く。
その後に、Task1のtaskSleepメソッド(taskNameはDog-2)が開始され、2秒後の10:06:52にメソッド処理が完了した後、ワンワンと鳴く。
つまり、2秒後にワンワンとニャンが同時に鳴き、更にその2秒後にニャンだけが鳴く。
このようにして、複数Taskを並列に実行することができる。
以上
]]>
iPhone などの iOS アプリ で Game Center と連携する際に、アプリがアクティブになったタイミングで、Leaderboard に保存してあるスコア値を取得・表示するコードを紹介する。
| 筆者のマシン | |
|---|---|
| Mac | mac mini (M1, 2020) |
| OS | Monterey バージョン 12.4 |
| XCode | バージョン 13.4 |
| Swift | バージョン 5.6.1 |
ここでは次の手順で説明する。
iPhoneアプリへ Game Center のスコアを表示する方法として、これまで下記記事を紹介してきた。
これらは、上記1.はプレイヤーのタップ動作が必要となり、上記2.は Game Center へのログイン時(通常、すでにログイン済みならアプリ起動時)にのみスコアを取得するものである。
そのため、ゲームアプリをバックグラウンドやインアクティブにした後に(つまりアプリを落とすことなく)、再度画面へ表示した場合には、最新のスコアを取得・表示することはできない。
そこで今回は、アプリがアクティブになったタイミングで、自動的に Game Center の Leaderboard から最新のスコアを読み込み表示する方法を紹介する。
尚、本記事で紹介するコード全体(XCodeプロジェクト)は、GitHubで公開している。
UIApplicationDelegateプロトコルには、アプリが inactive から active になったことを通知する static なプロパティ didBecomeActiveNotification がある。
今回はこの通知を利用する。
ここでは、アプリがアクティブになったら Game Center の Leaderboard にある最新のスコアを取得するために、次のように didBecomeActiveNotification を受信する Observer として、自作の func didBecomeActive() を実行させるようにする。
// add notification observer. When the App staus becomes active, works using block
NotificationCenter.default.addObserver(
forName: UIApplication.didBecomeActiveNotification,
object: nil,
queue: OperationQueue.main,
using: { _ in
print("App status becomes active.")
self.didBecomeActive()
}
)
こうすることで、バックグラウンドなどの非アクティブな状態からアクティブになった時(つまりユーザがアプリを全面に表示した時)に、func didBecomeActive()が実行される。
ここでは、アクティブになった時の通知を受信するために didBecomeActiveNotification を利用したが、UIApplicationDelegate プロトコルには、バックグラウンドになった時の通知(didEnterBackgroundNotification)やフォアグラウンドになる時の通知(willEnterForegroundNotification)が用意されているため、仕様に応じて使い分けることができる。
アクティブになった時の Observer である自作の func didBecomeActive() は下記である。
private func didBecomeActive() {
print("didBecomeActive.")
loadScore(scoreDidLoad: { local, entries in
// If entries are loaded, set the value to worldHighScore
if let entries = entries,
entries.isEmpty == false {
self.autoLoadScore2.worldScore = entries[0].score
}
// If local is loaded, set the value to yourHighScore
if let local = local {
self.autoLoadScore2.yourScore = local.score
}
})
}
3行目で、もう一つの自作メソッド
そして取得したあとに、7行目で、予め定義済みの変数に、Leaderboard から取得した全世界のトップスコアをセットしている。 また11行目で、予め定義済みの変数に、Leaderboard から取得したローカルプレイヤーのスコアをセットしている。
もう一つの自作メソッド
private func loadScore(scoreDidLoad: @escaping (_ localPlayerEntry: GKLeaderboard.Entry?, _ entries: [GKLeaderboard.Entry]?) -> Void) {
print("func loadScore")
if GKLocalPlayer.local.isAuthenticated {
print("You are logged in. Start to load scores from Game Center.")
// Get the Leaderboard ID of this app that you have previously registered in App Store Connect
// (only one ID is registered here).
GKLocalPlayer.local.loadDefaultLeaderboardIdentifier(completionHandler: { (leaderboardIdentifer, error) in
if error != nil { // Error occured
print(error!.localizedDescription)
} else { // Got the loaderBoarderIdentifier for this app.
// Load scores
if let leaderboarderId = leaderboardIdentifer {
print("Default leaderboardIdentifer is \(leaderboarderId).")
GKLeaderboard.loadLeaderboards(IDs: [leaderboarderId]) { (boards, _) in
boards?.first?.loadEntries(for: .global, timeScope: .allTime, range: NSRange(location: 1, length: 1), completionHandler: {(myLocalPlayerEntry, myEntries, _, _) in
scoreDidLoad(myLocalPlayerEntry, myEntries)
})
}
}
}
})
}
}
ログイン済みであれば Leaderboard からスコアを取得するように 下記3行目の if GKLocalPlayer.local.isAuthenticatedで認証済みかをチェックしている。
補足として、このコードでは、デフォルトの Leaderboard を使用している。また、筆者は便宜上 自作メソッドを2つにしているが、メソッドの分割は必須ではない。
これらによる画面の動きを下記の画面キャプチャで説明する。ここでは、すでに Game Center へはログイン済みである。
また赤枠部分が、今回紹介するコードで実現する箇所である。それ以外のところは、過去の記事(Apple の Game Center スコア値を取得する, Game CenterへログインしたタイミングでLeaderboardからスコアを取得する)にて紹介している
アプリ起動直後の画面
まずは起動直後の画面である。
画面の赤枠の worldScore は Leaderboard から取得した全世界のトップスコア、myScoreは Leaderboard から取得したローカルプレイヤーのスコアである。

起動後、画面が表示される際にアプリはアクティブになるので、上記の自作のメソッド func didBecomeActive() は一度実行されている。
しかし、上の画面キャプチャの場合もそうであるが、Game Center へのログイン処理完了前にこの自作メソッドが実行されるため、上記コード if GKLocalPlayer.local.isAuthenticatedの結果が false となり、Game Center からのスコア取得は行われない。従って、赤枠内のスコアはいずれもデフォルトの 0 を表示している。
補足:赤枠の上部にある青枠にはスコアが表示されているが、これは認証状態の変更を受信してスコアを取得・表示している。
アプリをインアクティブにする
ここで、アプリを終了させずに、アクティブ以外の状態にする。アクティブ以外の状態とは、画面を他のアプリに切り替えたり、起動中のアプリを選択する App スイッチャーの状態などが挙げられる。
アプリをアクティブにする
アプリをアクティブにする(つまり再びこのアプリ画面を表示する)と、上記で説明したように、通知didBecomeActiveNotificationを受信し、自動的に Leaderboard から最新のスコアを読み込み、画面キャプチャのように表示することができる。

尚、赤枠の上部の青枠部分は、Game CenterへログインしたタイミングでLeaderboardからスコアを取得するで説明したコードで紹介している。
また赤枠の下部の青枠部分は、ボタンをタップしたら Leaderboard からスコアを取得し表示するで紹介している。
以上
]]>