はじめに
この記事ではRealmを用いたiOSアプリにおける画像の保存・表示について扱います。
まぁまぁ苦労したので自分自身の備忘録的な意図が強いです。
保存
保存の仕方
まず、Realmで画像自体(UIImage型)を保存することはできません。
ではどのように保存するかというと今回はアプリ内で画像を保存する場所をString型として保存します!
画像を保存する際には、アプリ内にある画像を保存する場所(ディレクトリ)にデータが突っ込まれます。この時画像の名前を UUID().uuidStringなどユニークな値に設定してあげます。そして読み込む時にはその画像の場所(/ディレクトリのURL/画像の名前.jpeg みたいな形をしている)を指定してあげることで該当の場所にある画像を取り出すことができます。
コード
百聞は一見に如かず!コードです。
今回はピッカーで画像を選択したときに選択された画像を保存するようにしました。
// Model
import Foundation
import RealmSwift
class MyModel: Object {
@objc dynamic var imageURL: String = ""
@objc dynamic var id: String = UUID().uuidString
override static func primaryKey() -> String? {
return "id"
}
}
// 保存
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
if results.count != 0 {
results[0].itemProvider.loadDataRepresentation(forTypeIdentifier: "public.image") { data, _ in
DispatchQueue.main.async {
if let imageData = data, let image = UIImage(data: imageData) {
self.imageView.image = image
let fileName = UUID().uuidString
guard let url = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("\(fileName).jpeg") else { return }
let data = image.jpegData(compressionQuality: 1.0)
do {
try data?.write(to: url)
} catch let err {
print(err.localizedDescription)
}
try! self.realm.write({
let model = MyModel()
model.imageURL = "\(fileName).jpeg"
self.realm.add(model)
})
}
}
}
}
picker.dismiss(animated: true)
}
表示
保存のところで少しお話ししたように、取得したいデータの場所を指定してあげればいいだけです。
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
itemList = realm.objects(MyModel.self)
print("itemList: ", itemList!)
if itemList.count != 0 {
guard let url = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(itemList[0].imageURL) else { return } // 検索するだけなので、createはfalse
do {
let readData = try Data(contentsOf: url)
let readImage = UIImage(data: readData)
imageView.image = readImage
} catch let error {
print(error)
}
}
}
はまったポイント
私がはまったポイントはズバリ、ディレクトリの名称です。
上記のコードでは、
- 画像の名前をUUID().uuidString.jpegに設定
- 画像の名前自体をimageURLに保存
- 表示する際、再度ディレクトリに入ってその中で保存したデータの0番目の画像を表示する
という手順を踏んでいます。上手くいかなかった時は画像の名前ではなく、ファイルの場所(ディレクトリ+画像名の形)をそのまま保存していました。ところが確認してみると、なんとディレクトリフォルダの名称が実行するたびに変わっていたのです。ゆえに、Realmに保存してある画像の場所にアクセスしても保存したときのディレクトリ名と読み込んでいるときのディレクトリ名が異なっているので、画像が表示されませんでした。なぜディレクトリ名がコロコロ変わってしまっていたのかは謎です、、、
もっと簡単にすると、要は保存する時と表示の時で画像が保存してあるフォルダ名がなぜか変わっているから、Realmに保存するのは画像の名前だけ!保存時と表示時で都度都度該当のフォルダ名を読み込むようにしよう!ということです。
全体のコード
import UIKit
import PhotosUI
import RealmSwift
class ViewController: UIViewController, UIImagePickerControllerDelegate, PHPickerViewControllerDelegate {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var selectImageButton: UIButton!
let realm = try! Realm()
var itemList: Results<MyModel>!
var documentDirectoryFileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let filePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.imageView.clipsToBounds = true
self.imageView.contentMode = .scaleAspectFit
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
itemList = realm.objects(MyModel.self)
print("itemList: ", itemList!)
if itemList.count != 0 {
guard let url = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(itemList[0].imageURL) else { return } // 検索するだけなので、createはfalse
do {
let readData = try Data(contentsOf: url)
let readImage = UIImage(data: readData)
imageView.image = readImage
} catch let error {
print(error)
}
}
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
if results.count != 0 {
results[0].itemProvider.loadDataRepresentation(forTypeIdentifier: "public.image") { data, _ in
DispatchQueue.main.async {
if let imageData = data, let image = UIImage(data: imageData) {
self.imageView.image = image
let fileName = UUID().uuidString
guard let url = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("\(fileName).jpeg") else { return }
let data = image.jpegData(compressionQuality: 1.0)
do {
try data?.write(to: url)
} catch let err {
print(err.localizedDescription)
}
try! self.realm.write({
let model = MyModel()
model.imageURL = "\(fileName).jpeg"
self.realm.add(model)
})
}
}
}
}
picker.dismiss(animated: true)
}
@IBAction func tappedSelectImageButton(_ sender: UIButton) {
var configuration = PHPickerConfiguration()
configuration.selectionLimit = 1
configuration.filter = .images
configuration.preferredAssetRepresentationMode = .current
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
switch PHPhotoLibrary.authorizationStatus(for: .addOnly) {
case .notDetermined:
PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in
switch status {
case .authorized:
DispatchQueue.main.async {
self.present(picker, animated: true, completion: nil)
}
default:
print("denied")
}
}
case .authorized:
DispatchQueue.main.async {
self.present(picker, animated: true)
}
case .denied:
let alert = UIAlertController(title: "Cannot access photo library", message: "Please allow accsess from Settings" , preferredStyle: .alert)
let settings = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
let settingsURL = URL(string: UIApplication.openSettingsURLString)
UIApplication.shared.open(settingsURL!, options: [:], completionHandler: nil)
}
let close: UIAlertAction = UIAlertAction(title: "cancel", style: .cancel, handler: nil)
alert.addAction(settings)
alert.addAction(close)
self.present(alert, animated: true, completion: nil)
case .restricted:
let alert = UIAlertController(title: "Cannot access photo library", message: "", preferredStyle: .alert)
let close: UIAlertAction = UIAlertAction(title: "cancel", style: .cancel, handler: nil)
alert.addAction(close)
self.present(alert, animated: true, completion: nil)
default: break
}
}
}