ワンクリックで
ios-pitfalls
このプロジェクトで実際に踏んだ iOS 開発の落とし穴と対策。CoreData, AVFoundation, AVAudioSession など。CoreData の設定変更・新機能追加・マイグレーション対応時に必ず参照すること。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
このプロジェクトで実際に踏んだ iOS 開発の落とし穴と対策。CoreData, AVFoundation, AVAudioSession など。CoreData の設定変更・新機能追加・マイグレーション対応時に必ず参照すること。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
iOSシミュレーターをコンピューターユースで視覚的にテストします。スクリーンショットを撮影してUI要素を確認し、座標ベースでタップ・スワイプ操作を行いテスト結果を報告します。Use when ユーザーが「シミュレーターでテストして」「iOSシミュレーターを視覚的にテストして」「computer useでシミュレーターを操作して」「シミュレーターの画面確認して」と言ったとき。
Xcode MCP(mcpbridge)を Claude Code に追加します。Use when ユーザーが「Xcode MCP入れて」「シミュレーター操作できるようにして」「mcpbridge設定して」と言ったとき。
xcodebuildとfastlaneでiOSアプリをビルド・アーカイブし、App Store/TestFlightに申請します。Use when ユーザーが「TestFlightにアップして」「App Storeに申請して」「リリースして」と言ったとき。
Step-by-step release checklist for ClipKit iOS app submission to TestFlight and App Store. Use when preparing release, uploading to TestFlight, submitting to App Store, bumping version, or creating a release.
Step-by-step release checklist for Smart Player iOS app submission to App Store. Use when preparing release, updating version, submitting to App Store, or mentioning App Store submission.
コード変更後にXcodeビルドを実行して、コンパイルエラーや警告を確認します。-skipMacroValidationフラグを使用してComposableArchitectureマクロの検証をスキップします。Use when ビルド確認、コンパイル、ビルドエラー、変更後の検証。
| name | ios-pitfalls |
| description | このプロジェクトで実際に踏んだ iOS 開発の落とし穴と対策。CoreData, AVFoundation, AVAudioSession など。CoreData の設定変更・新機能追加・マイグレーション対応時に必ず参照すること。 |
| metadata | {"author":"Smart Player Team","version":"1.0.0","category":"development","tags":["ios","coredata","avfoundation","pitfalls","bugfix"]} |
このプロジェクトで実際に発生したバグとその原因・対策を記録する。 同じバグを繰り返さないために、関連する作業の前に必ず参照すること。
アプリを再起動するたびにダウンロードしたファイルとリストが消える。
CoreData のマイグレーション設定を追加しようとして、URL なしの NSPersistentStoreDescription() を作成し persistentStoreDescriptions を上書きした。
// ❌ NG: URL なしの description で上書き → インメモリストアになる
let description = NSPersistentStoreDescription() // URL が nil!
description.shouldMigrateStoreAutomatically = true
description.shouldInferMappingModelAutomatically = true
container.persistentStoreDescriptions = [description] // デフォルトを破壊
URL が nil の description を使うと、CoreData はデフォルトの SQLite ファイルパス
(Library/Application Support/VideoModel.sqlite)を失い、インメモリストアで動作する。
アプリを再起動するとメモリが消えてデータが全滅する。
NSPersistentContainer はデフォルトで lightweight migration が有効なので、
何も追加しなくていい。description を上書きしてはいけない。
// ✅ OK: 何も上書きしない
init() {
container = NSPersistentContainer(name: "VideoModel")
container.loadPersistentStores { _, error in
if let error { print("Core Data failed to load: \(error)") }
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
}
もし description を変更する必要がある場合は、必ず既存の description を取得して変更する。 新規作成して置き換えてはいけない。
// ✅ OK: 既存の description を変更
container.persistentStoreDescriptions.first?.shouldMigrateStoreAutomatically = true
PersistenceControllerTests.swift に以下のテストを追加済み:
testPersistentStoreHasFileURL() — ストア URL が nil でないことを検証testDataPersistsAcrossControllerReinit() — 再起動後もデータが残ることを検証ダウンロードした動画を再生しても音が出ない(特にサイレントモード時)。
AVAudioSession のデフォルトカテゴリは .soloAmbient で、
iOS のサイレントスイッチ(マナーモード)がオンのときミュートされる。
アプリ起動時(AppDelegate.didFinishLaunchingWithOptions)に .playback カテゴリを設定する。
// videoPlayerApp.swift AppDelegate 内
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("[AudioSession] Failed to configure: \(error)")
}
HLS ダウンロード(.movpkg 形式)の動画一覧でサムネイルが表示されない。
AVAssetImageGenerator.copyCGImage(at:actualTime:) の同期 API は、
AVAssetDownloadTask でダウンロードした .movpkg(HLS オフラインバンドル)では動作しない。
AVURLAsset に変更し、load(.tracks) で非同期にトラック情報を先読みしてから
generateCGImagesAsynchronously を使う。
// ✅ OK
Task {
let asset = AVURLAsset(url: url)
_ = try? await asset.load(.tracks) // .movpkg はこれが必要
let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true
imageGenerator.maximumSize = CGSize(width: 240, height: 160)
let time = CMTime(seconds: 1, preferredTimescale: 600)
await withCheckedContinuation { continuation in
imageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: time)]) { _, cgImage, _, result, _ in
if result == .succeeded, let cgImage {
Task { @MainActor in thumbnail = UIImage(cgImage: cgImage) }
}
continuation.resume()
}
}
}