📸

SwiftUIでカメラで写真を撮影してRealmに保存する

2024/03/08に公開

読んでほしい人

  • SwiftUIでカメラ機能を使うのに興味がある
  • ローカルDBに保存する方法を知りたい
  • x-codeの設定で、info.plistやxmlの設定をやったことある人

補足情報

カメラ機能を使うにはinfo.plistの設定が必要なので、以前私が書いた記事を参考にすると良いかもしれないです。
https://zenn.dev/joo_hashi/articles/cbb87247dd9418
Realemを追加するなら、こちらを参考にすると良いと思います。
https://zenn.dev/joo_hashi/articles/0ce2f6c4fb8615

私は動作検証は実機でおこなっているので、iPhoneの実機があった方が良いです!

記事の内容

今回は、UIKitの知識も必要みたいでSwiftUIだけでカメラ機能を作ることはできません。AIに聞きながら作りましたが、なんとか動くものは作れましたね。機能としては、写真の撮影をして、表示・削除ができるアプリケーションになっております。

Realmを追加したら、画像を保存するのに使うモデルを定義しましょう。画像の削除をするときに、UUIDが必要なので、プロパティに追加しておきます。

画像の保存をするモデル
import Foundation
import RealmSwift
// 写真を保存するモデル
class Photo: Object, Identifiable {
    @Persisted(primaryKey: true) var id: String = UUID().uuidString
    @Persisted var imageData: Data?
}

画像を保存したときに、画面の更新をするライフサイクルが必要なので、ViewModelで状態管理をします。ロジック書いてたら、ViewModelではない気がするが...

状態管理のコード
import SwiftUI
import RealmSwift

// 写真の保存、表示、削除の状態を扱うViewModel
class PhotoViewModel: ObservableObject {
    @Published var photos: Results<Photo> = try! Realm().objects(Photo.self)
    private var realm = try! Realm()
    // 撮影した写真を保存するメソッド
    func saveImage(_ imageData: Data) {
        let photo = Photo()
        photo.imageData = imageData
        
        try! realm.write {
            realm.add(photo)
        }
    }
    // 保存した写真を削除するメソッド
    func deletePhoto(_ photo: Photo) {
        guard let photoToDelete = realm.object(ofType: Photo.self, forPrimaryKey: photo.id) else {
            return
        }
        
        try! realm.write {
            realm.delete(photoToDelete)
        }
        
        // photosプロパティを更新する
        photos = getAllPhotos()
    }


    // 保存した写真を全て表示するメソッド
    func getAllPhotos() -> Results<Photo> {
        return realm.objects(Photo.self)
    }
}

ImagePickerなるものを使うには、UIKitのコードが必要なので、仕組みはよくわからないが、写真撮影したら、ボタンを押すと画面を閉じるロジックを後で追加しました。どうやら、UIKitのコードで写真を撮ったら画面を閉じるのを制御するようです。

ImagePickerの構造体
import SwiftUI
import UIKit

// ImagePickerを使うための構造体
struct ImagePicker: UIViewControllerRepresentable {
    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        let imageData: (Data?) -> Void
        let parent: ImagePicker
        
        init(parent: ImagePicker, imageData: @escaping (Data?) -> Void) {
            self.parent = parent
            self.imageData = imageData
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let image = info[.originalImage] as? UIImage, let imageData = image.jpegData(compressionQuality: 0.5) {
                self.imageData(imageData)
            } else {
                self.imageData(nil)
            }
            
            parent.presentationMode.wrappedValue.dismiss()
        }
        
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            self.imageData(nil)
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
    
    @Environment(\.presentationMode) var presentationMode
    let imageData: (Data?) -> Void
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.sourceType = .camera
        picker.delegate = context.coordinator
        return picker
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self, imageData: imageData)
    }
}

こちらのコードで、写真の撮影をして保存、表示、削除をおこなうUIを作っています。

Viewのコード
import SwiftUI

// 写真撮影をして、表示・削除をするページ
struct ContentView: View {
    @StateObject private var photoViewModel = PhotoViewModel()
    @State private var showingImagePicker = false
    
    var body: some View {
        NavigationView {
            List {
                ForEach(photoViewModel.getAllPhotos()) { photo in
                    if let imageData = photo.imageData, let uiImage = UIImage(data: imageData) {
                        VStack {
                            Image(uiImage: uiImage)
                                .resizable()
                                .scaledToFit()
                                .frame(height: 200)
                                .cornerRadius(10)
                                .padding()
                            Button("削除") {
                                photoViewModel.deletePhoto(photo)
                            }
                        }
                    }
                }
            }
            .navigationTitle("Photos")
            .navigationBarItems(trailing:
                Button("Take Photo") {
                    showingImagePicker.toggle()
                }
            )
            .sheet(isPresented: $showingImagePicker) {
                ImagePicker(imageData: { imageData in
                    if let imageData = imageData {
                        photoViewModel.saveImage(imageData)
                        // ビューモデルの更新をトリガーする
                        photoViewModel.objectWillChange.send()
                    }
                })
            }
        }
    }
}

動作はこんな感じです。



最後に

今回は、あまり情報がないRealmにカメラで撮影した写真を保存するロジックを作ってみました。これがStoryboardだと難しいんでしょうね。コードを書くだけで開発できるSwiftUIは開発体験が良かったですね。x-cocdの設定をいじらないといけない場面もありましたが💦

Discussion