🧑🏻💻 個人開発メモ
SwiftUIのローカライズ(多言語)対応
古い情報↓
アプリ表示はこちら。
InfoPlist
の多言語対応は、各言語用のInfoPlist
のstrings
ファイルを作ればOK。
"CFBundleDisplayName" = "Password Gen";
"NSUserTrackingUsageDescription" = "This identifier will be used to deliver personalized ads to you.";
Xcode Tips
キーボードショートカット
- シミュレータのライト / ダークモードの切り替え:
command
+shift
+A
Core Dataで画像を保存するときのTips
SwiftでRevenueCatを使うとき、StoreKit 2を利用するオプションは使わないほうが安定しそう。
// 🙆♂️ 挙動安定
Purchases.configure(withAPIKey: <public_apple_api_key>)
// 🙅♂️ 挙動不安定
Purchases.configure(
with: Configuration.Builder(withAPIKey: <public_apple_api_key>)
.with(usesStoreKit2IfAvailable: true)
.build()
)
詳細な原因まではわからなかったが、Sandbox環境でまれにエラーが発生した。
[Purchases] - WARN: 🍎⚠️ StoreKit 2 purchase took longer than expected (21.13 seconds)
...
[Purchases] - ERROR: 😿‼️ The receipt is not valid.
...
[Purchases] - ERROR: 💰 Product purchase for 'xxx' failed with error: Error Domain=RevenueCat.ErrorCode Code=8 "The receipt is not valid." UserInfo={readable_error_code=INVALID_RECEIPT, NSLocalizedDescription=The receipt is not valid., source_file=RevenueCat/HTTPClient.swift:379, rc_backend_error_code=7103, source_function=convertUnsuccessfulResponseToError(), rc_response_status_code=400, NSUnderlyingError=0x280c68720 {Error Domain=RevenueCat.BackendErrorCode Code=7103 "The receipt is not valid." UserInfo={rc_backend_error_code=7103, NSLocalizedDescription=The receipt is not valid.}}}
loading end
iOSでサブスク機能実装したアプリがAppleの審査でリジェクトされた。理由は以下のとおり。利用規約を用意する必要があるとのこと。
Guideline 3.1.2 - Business - Payments - Subscriptions
We noticed that your app did not meet all the terms and conditions for auto-renewing subscriptions, as specified in Schedule 2, section 3.8(b) of the Paid Applications agreement.
We were unable to find the following required item(s) in your app's metadata:
– A functional link to the Terms of Use (EULA)
Next Steps
To resolve this issue, please add this missing information. If the above information is present, please reply to this message in App Store Connect to provide details on where to locate it.
If you are using the standard Apple Terms of Use (EULA), you will need to include a link to the Terms of Use in your App Description. If you are using a custom EULA, add it in App Store Connect.
対応方法は以下のとおり。
SwiftUIでUserDefaultsを使うときのTips
iOSの場合、アプリにはじめてサブスクリプションを導入する際、本番環境でサブスクリプションが機能するまでAppleの承認後24時間ほどかかるみたい。自分のアプリでサブスクリプションが機能していないのはこのせいかも。
2023/2/15追記
サブスクリプション承認後に、27時間後 or アプリ空リリースのための審査提出(審査待ち)で本番環境で機能しはじめた。どちらが効いたのかはわからなかった。
App StoreのプロダクトページのURL
Info.plistを「Source Code」で開いて変更するとタブやスペースが混在することがある。その場合、「Property List」で開き適当なKeyを追加→削除するとタブに統一される。
SwiftDataに関するApple公式のサンプルコード
SwiftDataでCloudKitを使うときのモデル設計に関する注意点
- @Attribute(.unique)は利用できない
- @Relationshipの関係にあるすべてのフィールドはオプショナル型にしないといけない
- すべてのフィールドは初期値を設定するかオプショナル型にしないといけない
CloudKitを有効化して実行したときのエラー Invalid bundle ID for container
の対処法。以下で解決した。
Privacy manifest filesのNSPrivacyTrackingDomainsを調査する手順
Flutterでのリリース
# iOS
$ flutter build ipa
# Android
$ flutter build appbundle
# デバッグシンボルをアップロード用に加工する
$ cd build/app/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib
$ zip -r Archive.zip arm64-v8a armeabi-v7a x86_64
# Play Storeで手動アップロードしやすい場所にコピーしてくる
$ cp build/app/outputs/bundle/release/app-release.aab ~/Documents
$ cp build/app/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib/Archive.zip ~/Documents
1つのApple IDで複数回オファーコードを適用させる場合、適用中であればそのサブスクリプションを解約し、別のオファーコードで申し込めばOK。
Androidでアプリ内課金を有効化するときは、AndroidManifest.xmlに設定を追加する必要がある。追加しない場合だと、Google Play Consoleの「アプリ内アイテム」でアイテムを作成するメニューが出ない。追加後、クローズド テストでトラックを作成するとメニューが表示される。
<uses-permission android:name="com.android.vending.BILLING" />
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- ここ -->
<uses-permission android:name="com.android.vending.BILLING" />
<application
...
</application>
</manifest>
課金パッケージの依存性を追加しないとPlay Store上でApp Bundleをアップロードした後に「このアプリで現在使用している Play Billing Library のバージョンは AIDL です。Google Play で最新の収益化機能を利用するには、バージョン 6.0.1 以上に更新する必要があります。」エラーが発生する。
implementation("com.revenuecat.purchases:purchases:8.15.1")
Androidのアプリアイコンの設定方法
Image Asset Studioでアダプティブ ランチャー アイコンとレガシー ランチャー アイコンを作成する。
Background Layer(Resizeは100%)→Foreground Layer(Resizeは69%前後がよさそう)の順番で作成するとわかりやすい。
ネイティブのAndroidアプリをビルドするときはAndroid Studioのメニューの「Build」→「Generate Signed App Bundle or APK...」から行う。
アップロード鍵とキーストアはAndroid Studioから以下を参考に作成する。例えば、プロジェクト直下のkeystores/upload-keystore.jksとして保存する。⚠️ git管理しないこと
app/release/app-release.aab
(App Bundle)が生成される。
Jetpack Composeでよく使うプロジェクト構成
app/
├── src/
│ ├── main/
│ │ ├── java/com/donchan922/myapp/
│ │ │ ├── data/
│ │ │ │ ├── local/
│ │ │ │ │ ├── dao/
│ │ │ │ │ ├── entity/
│ │ │ │ │ └── AppDatabase.kt
│ │ │ │ ├── model/
│ │ │ │ ├── preferences/
│ │ │ │ │ └── AppPreferences.kt
│ │ │ │ └── repository/
│ │ │ ├── di/
│ │ │ │ └── AppContainer.kt
│ │ │ ├── ui/
│ │ │ │ ├── components/
│ │ │ │ ├── screens/
│ │ │ │ │ ├── home/
│ │ │ │ │ │ ├── HomeScreen.kt
│ │ │ │ │ │ └── HomeViewModel.kt
│ │ │ │ │ └── AppViewModelProvider.kt
│ │ │ │ └── theme/
│ │ │ ├── utils/
│ │ │ └── MainActivity.kt
│ │ └── res/
│ └── test/
Androidの多言語化対応
デフォルト言語を英語、そこに日本語を追加する場合
-
res/values-ja/strings.xml
を作成する
<resources>
<string name="app_name">アプリ名</string>
</resources>
- 各ファイルから読み込む
// Composeで使用する場合
Text(stringResource(id = R.string.app_name))
or
// それ以外の場合
R.string.app_name
Androidでデータベースの中身を操作できるAndroid Studioの機能
Androidリリース時の警告の対応方法
この App Bundle に関連付けられている難読化解除ファイルはありません。難読化コード(R8 / ProGuard)を使用している場合、難読化解除ファイルをアップロードすると、クラッシュと ANR をより簡単に分析、デバッグできるようになります。R8 / ProGuard の使用は、アプリサイズの縮小につながります。
buildTypes {
release {
- isMinifyEnabled = false
+ isMinifyEnabled = true
}
}
app/build/outputs/mapping/release/mapping.txt
をアップロードすればOK
この App Bundle にはネイティブ コードが含まれ、デバッグ シンボルがアップロードされていません。クラッシュや ANR を簡単に分析、デバッグできるよう、シンボル ファイルをアップロードすることをおすすめします。
↑の手順だとnative-debug-symbols.zipが出力されなかったので自分で生成する。native-debug-symbols.zip
をアップロードすればOK
cd app/build/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib
zip -r native-debug-symbols.zip arm64-v8a armeabi-v7a x86_64
Androidでマテリアルアイコンを追加したい場合は、resディレクトリ右クリック→New→Vector Asset→Clip artから対象のアイコンを選択
Icon(
painter = painterResource(id = R.drawable.baseline_workspace_premium_24),
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(end = 8.dp)
)