0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SwiftDataに画像や映像を保存する

Last updated at Posted at 2025-01-16

どうも!こんにちは!

題名通り、SwiftDataに画像や映像を保存してランニングコストを極限まで落とそう!というゲスいことを考えています。(個人開発を始めようと画策中な今日この頃

画像や映像をアプリで表示するのに、サーバー借りたり、Supabaseの有料プラン登録するのめんどくさいですよね〜

そんなあなたに向けて、今日はこの記事をお届けます

では、行ってみましょう〜

Swift Dataのモデルを作成

SwiftDataのセットアップは他の記事見てください。まとまっている記事をよく見かけるので(適当)

Item.swift
import Foundation
import SwiftData

@Model
internal final class Item {
    var id: UUID = UUID()
    var videoData: Data = Data()

    init(i: UUID, videoData: Data) {
        self.id = id
        self.videoData = videoData
    }
}

はい、ここで察しの良い方は気付きましたね。
そう、Data型で登録してしまえばいいんです

私は、Swift、Appleデバイス向けアプリ開発と心中する予定なのでこれでOK

映像データの取得

PhotosPickerから映像や画像を選択できるようにしてあげて〜

view.swift
struct AddDataView: View {
    @State var viewModel: ViewModel
    VSTack {
        PhotosPicker(
            selection: $viewModel.selectedItems,
            maxSelectionCount: 1,
            matching: .videos,
            photoLibrary: .shared()
        ) {
            Text("映像紐付け")
        }
        // ...省略
    }
    .onChange(of: viewModel.selectedItems, { _, new in
        viewModel.addItem(pickerItems: new)
    })
}

SwiftDataへ登録

登録する情報をまとめて〜

viewModel.swift
@Observable
@MainActor
internal final class MotionChartViewModel {
   var selectedItems: [PhotosPickerItem] = []
   func addItem(pickerItems: [PhotosPickerItem]) {
       Task {
           do {
               guard let video: PhotosPickerItem = pickerItems.first else {
                   return
               }
               guard let videoData = try await video.loadTransferable(type: Data.self) else {
                   return
               }
               let item: Item = Item(
                   id: UUID(),
                   videoData: videoData
               )
               let service: Service = .init()
               service.addItem(item)
           } catch {
               print(error)
           }
       }
   }
}

サービス経由でデータを登録してあげるぅ〜

Service.swift
import Foundation
import SwiftData

internal class MotionVideoService {
    var modelContext: ModelContext?
    var modelContainer: ModelContainer?

    @MainActor
    init() {
        do {
            let configuration: ModelConfiguration = ModelConfiguration(isStoredInMemoryOnly: false)
            let container: ModelContainer = try ModelContainer(
                for: Item.self, configurations: configuration
            )
            modelContainer = container
            modelContext = container.mainContext
            modelContext?.autosaveEnabled = true
        } catch {
            print(
                "DEBUG: init failed: \(error.localizedDescription)"
            )
        }
    }

    func addItem(_ item: Item) {
        guard let modelContext: ModelContext else {
            return
        }
        modelContext.insert(item)
        save()
    }

    private func save() {
        guard let modelContext: ModelContext else {
            return
        }
        do {
            try modelContext.save()
        } catch {
            print("DEBUG: Failed to save: \(error.localizedDescription)")
        }
    }
}

登録処理は上記で完了!!

データの取得

ID指定で保存したデータを取得して〜

Service.swift
import Foundation
import SwiftData

internal class MotionVideoService {
    // ...省略

    func fetchMotionVideo(id: UUID) -> Item? {
        do {
            guard let modelContext: ModelContext else {
                return nil
            }
            // 条件を指定して fetch
            let filteredItems: [Item] = try modelContext.fetch(
                FetchDescriptor<Item>(
                    predicate: #Predicate { motion in
                        motion.motionId == id
                    }
                )
            )
            return filteredItems.first
        } catch {
            return nil
        }
    }
    
    // ...省略
}

Viewに反映

viewModelでSwiftDataから値を取得して

viewModel.swift
@Observable
@MainActor
internal final class ViewModel {
    let motionVideoPlayer: AVPlayer?

    init(motion: MotionItem) {
        do {
            guard let video = motionVideoService.fetchMotionVideo(id: motion.id) else {
                throw MyError.videoNotFound
            }
            let tempURL: URL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".mov")
            try video.videoData.write(to: tempURL)
            self.motionVideoPlayer = AVPlayer(url: tempURL)
        } catch {
            print("error is \(error)")
            self.motionVideoPlayer = nil
        }
        
    }
}

Viewに反映させる

view.swift
if let player = viewModel.motionVideoPlayer {
    VideoPlayer(player: player)
        .frame(height: 200)
        .onAppear {
            player.play()
        }
        } else {
            Text("映像を再生できません")
        }
}

これで映像が表示されるはず〜
ローカルに適宜保存してあげればいいじゃん、って意見もあるだろうけど、

SwiftDataと連携してIDなどを外部キーに指定して保存する場合はこっちのが管理楽だな〜と、
あとは、Viewから離れた際にファイルの削除処理などをはさんであげる
(消し忘れると、容量圧迫しちゃう...

https://forums.developer.apple.com/forums/thread/743014

ForumではAVPlayerを使わずに映像を表示する方法を見つけたが、m未検証(気が向いたらやる)

画像を保存する場合

1. PhotospickerのOptionを
matching: .videos,

matching: .images,
に変更して

2. 取得したものを同様にData型に変換
3. SwiftDataへ保存して
4. 画面へ表示する際にpngDatajpegDataに変換してからImageとして表示すればOK

こんな感じでできるはず〜
SwiftDataは触り始めだから、色々見落としているかもしれないですので引き続き使って何かいい方法見つかったら記事にしていこうかな〜

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?