원클릭으로
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()
}
}
}