📸
SwiftUIでカメラで写真を撮影してRealmに保存する
読んでほしい人
- SwiftUIでカメラ機能を使うのに興味がある
- ローカルDBに保存する方法を知りたい
- x-codeの設定で、info.plistやxmlの設定をやったことある人
補足情報
カメラ機能を使うにはinfo.plist
の設定が必要なので、以前私が書いた記事を参考にすると良いかもしれないです。
Realemを追加するなら、こちらを参考にすると良いと思います。
私は動作検証は実機でおこなっているので、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