LoginSignup
124
113

More than 5 years have passed since last update.

Realm × Swift2 でシームレスに画像を保存する

Last updated at Posted at 2015-11-04

2017/5/23追記

もともとのコードだと、トランザクションを意識しにくかったので、最近はこんなの作ってやってます

//
//  RealmExtension.swift
//  Musubi
//
//  Created by はるふ on 2016/10/27.
//  Copyright © 2016年 はるふ. All rights reserved.
//

import RealmSwift
import Realm
import UIKit

//
// MARK: protocols
//

protocol RealmEncodable {
    associatedtype EncodedType: Object
    associatedtype DecodedType: Self // DecodedTypeじゃなくてSelfにしたいけどno-finalクラスを束縛できない
    func realmEncode() -> EncodedType
    static func realmDecode(value: EncodedType) -> DecodedType
}

//
// MARK: Encodable Objects
//

class StringObject: Object {
    dynamic fileprivate var string = ""
}

extension String: RealmEncodable {
    typealias EncodedType = StringObject
    typealias DecodedType = String

    func realmEncode() -> StringObject {
        let o = StringObject()
        o.string = self
        return o
    }

    static func realmDecode(value: StringObject) -> String {
        return value.string
    }
}


class UIImageObject: Object {
    typealias DecodedType = UIImage
    dynamic fileprivate var data: Data!
}

extension UIImage: RealmEncodable {
    typealias EncodedType = UIImageObject
    typealias DecodedType = UIImage

    func realmEncode() -> UIImageObject {
        let o = UIImageObject()
        o.data = UIImagePNGRepresentation(self)
        return o
    }

    static func realmDecode(value: UIImageObject) -> UIImage {
        return UIImage(data: value.data)!
    }
}

//
// MARK: extention methods
//

extension Sequence where Iterator.Element: RealmEncodable, Iterator.Element.EncodedType: Object {
    typealias EncodedType = List<Iterator.Element.EncodedType>
    typealias DecodedType = Array<Iterator.Element.DecodedType>

    func realmEncode() -> EncodedType {
        return List<Iterator.Element.EncodedType>(self.map { $0.realmEncode() })
    }

    static func realmDecode(value: EncodedType) -> DecodedType {
        return value.map { Iterator.Element.realmDecode(value: $0) }
    }
}

extension Optional where Wrapped: RealmOptionalType {
    typealias DecodedType = Wrapped
    typealias EncodedType = RealmOptional<Wrapped>

    func realmEncode() -> RealmOptional<Wrapped> {
        return RealmOptional<Wrapped>(self)
    }

    static func realmDecode(value: RealmOptional<Wrapped>) -> Optional<Wrapped> {
        return value.value
    }
}

はじめに

1つめの記事 → Realm × Swift2 でデータを保存してみる
2つめの記事 → Realm × Swift2 でidでデータを管理する
の続きです。(最終のコードに含まれているだけで、読む必要はありません)

1つめの記事で述べたとおり、現在のバージョン(0.96.0)では、

String, NSString
Int
Float
Double
Bool
NSDate
NSData
RealmOptional for optional numeric properties
Object subclasses for to-one relationships
List for to-many relationships

しか保存できない。すなわち、その他の型を持つプロパティは、
存在するデータ形式に変換する必要がある。

今回の記事では、UIImageをNSDataとして保存し、取り出してUIImageにすることを目的とする。

Object

Realmで保存可能なオブジェクトとしては、Objectクラスを継承しなければならない。
前回同様、Userクラスを用いる。今回は、更にimageを加えたものにする。

User.swift
class User: Object {
    dynamic var id = 1
    dynamic var name = ""
    dynamic var image: UIImage? = nil
}

これを保存したい。

UIImage -> NSData

標準メソッドを使うだけで、JPEG形式またはPNG形式として、NSDataに変換できる。
JPEGでは画質が荒れてしまうので、PNGを用いる。

let data = UIImagePNGRepresentation(image)

NSData -> UIImage

dataからのUIImageの生成も、標準で用意されている。

let image = UIImage(data: data)

実装

NSDataを保存したいしたいので、これをUserクラスに加える。

dynamic private var imageData: NSData? = nil

ただ、imageをセットするたびに変換してセットするのは面倒なので、setter/getterを用いて自動的に行うため、以下のような実装にした。

dynamic private var _image: UIImage? = nil
dynamic var image: UIImage? {
    set{
        self._image = newValue
        if let value = newValue {
            self.imageData = UIImagePNGRepresentation(value)
        }
    }
    get{
        if let image = self._image {
            return image
        }
        if let data = self.imageData {
            self._image = UIImage(data: data)
            return self._image
        }
        return nil
    }
}
dynamic private var imageData: NSData? = nil

set

imageにsetすると、自動的に_imageに値が保持され、imageDataにも変換&setされる。

get

imageからgetすると、_imageがnilでなければ_imageを返す。
nilならimageDataから生成して_imageにセット&返す。
これにより、Realmはロード時にimageDataにしかsetしないが、imageからgetするだけでデータを利用することができる。

注釈

setter/getterで書いても、Realmは値を保存しない。
すなわち、

dynamic private var imageData: NSData? {
    return UIImagePNGRepresentation(self.image)
}

では保存されない。

ignoredProperties

Realmでは、保存しないプロパティを設定することができる。imageや_imageは保存しないので、ignoreさせておく。

override static func ignoredProperties() -> [String] {
    return ["image", "_image"]
}

完成品

User.swift
import UIKit
import RealmSwift

class User: Object {
    static let realm = try! Realm()

    dynamic private var id = 0
    dynamic var name = ""
    dynamic private var _image: UIImage? = nil
    dynamic var image: UIImage? {
        set{
            self._image = newValue
            if let value = newValue {
                self.imageData = UIImagePNGRepresentation(value)
            }
        }
        get{
            if let image = self._image {
                return image
            }
            if let data = self.imageData {
                self._image = UIImage(data: data)
                return self._image
            }
            return nil
        }
    }
    dynamic private var imageData: NSData? = nil

    override static func primaryKey() -> String? {
        return "id"
    }

    override static func ignoredProperties() -> [String] {
        return ["image", "_image"]
    }

    static func create() -> User {
        let user = User()
        user.id = lastId()
        return user
    }

    static func loadAll() -> [User] {
        let users = realm.objects(User).sorted("id", ascending: false)
        var ret: [User] = []
        for user in users {
            ret.append(user)
        }
        return ret
    }

    static func lastId() -> Int {
        if let user = realm.objects(User).last {
            return user.id + 1
        } else {
            return 1
        }
    }

    func save() {
        try! User.realm.write {
            User.realm.add(self)
        }
    }
}

ViewController.swift
import UIKit
import RealmSwift

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let a = User.create()
        a.name = "name"
        a.image = UIImage(named: "icon1.png")
        a.save()

        let b = User.create()
        b.name = "name"
        b.image = UIImage(named: "icon2.png")
        b.save()


        let users = User.loadAll()
        for (i, user) in users.enumerate() {
            let imageView = UIImageView()
            imageView.frame = CGRectMake(0,CGFloat(100*i),100,100)
            imageView.image = user.image
            self.view.addSubview(imageView)
        }
    }
}


124
113
1

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
124
113