文字っぽいの。

文字を書いています。写真も混ざります。

Swift4のCodableをUIImageに対応させる

環境

  • Xcode9 GM
  • Swift4

前提

Swift4からCodableという便利Protocolが追加されました。

public protocol Encodable {
    public func encode(to encoder: Encoder) throws
}

public protocol Decodable {
    public init(from decoder: Decoder) throws
}

public typealias Codable = Decodable & Encodable

やりたいこと

UIImageをプロパティに持つstructをCodable protocolに準拠させたい場合があります。

struct Photo {
    let id: Int
    let title: String
    let image: UIImage?
}

前述した通りCodableは、DecodableEncodableの2つのprotocolを要求するprotocolであるため、DecodableEncodableのそれぞれに準拠すれば良いです。

ここで、UIImageが邪魔になってきます。IntStringDecodable / Encodableに対応していますが、UIImageはしていません。なので、自分で処理を書く必要があります。

ちなみに、Custom TypeをCodableに対応させる方法はAppleのドキュメントにあるので、そちらを見ると良いです。

structで対応させる場合

こんな感じで、UIImageはbase64で文字列にエンコードしてあげます。簡単ですね。

extension Photo: Decodable {
    enum CodingKeys: String, CodingKey {
        case id
        case title
        case image
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(Int.self, forKey: .id)
        title = try values.decode(String.self, forKey: .title)

        let imageDataBase64String = try values.decode(String.self, forKey: .image)
        if let data = Data(base64Encoded: imageDataBase64String) {
            image = UIImage(data: data)
        } else {
            image = nil
        }
    }
}

extension Photo: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(title, forKey: .title)

        if let image = image, let imageData = UIImagePNGRepresentation(image) {
            let imageDataBase64String = imageData.base64EncodedString()
            try container.encode(imageDataBase64String, forKey: .image)
        }
    }
}

このサンプルコードでは、DecodableEncodableをわざわざ分けて書いていますが、

extension Photo: Codable {
}

とすれば、分けて書かなくて良くなります。ちなみに、UIImageそのものをCodableに対応させようとあれこれ試したのですが、うまくいきませんでした。強い人教えてくれ!

余談その1

structではなくclassをCodableに対応させる場合、すこしだけ記法が変わります。

final class Photo {
    let id: Int
    let title: String
    let image: UIImage?

    init(id: Int, title: String, image: UIImage?) {
        self.id = id
        self.title = title
        self.image = image
    }
}

extension Photo: Codable {

    // Decodable
    enum CodingKeys: String, CodingKey {
        case id
        case title
        case image
    }

    convenience init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        let id = try values.decode(Int.self, forKey: .id)
        let title = try values.decode(String.self, forKey: .title)

        let imageDataBase64String = try values.decode(String.self, forKey: .image)
        let image: UIImage?
        if let data = Data(base64Encoded: imageDataBase64String) {
            image = UIImage(data: data)
        } else {
            image = nil
        }
        self.init(id: id, title: title, image: image)
    }

    // Encodable
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(title, forKey: .title)

        if let image = image, let imageData = UIImagePNGRepresentation(image) {
            let imageDataBase64String = imageData.base64EncodedString()
            try container.encode(imageDataBase64String, forKey: .image)
        }
    }

}

余談その2

上記サンプルコードではUIImagePNGRepresentation を使って決め打ちで画像を変換していますが、

extension UIImage {

    private var hasAlpha: Bool {
        guard let alphaInfo = cgImage?.alphaInfo else {
            return false
        }

        switch alphaInfo {
        case .first, .last, .premultipliedFirst, .premultipliedLast, .alphaOnly:
            return true
        case .none, .noneSkipFirst, .noneSkipLast:
            return false
        }
    }

    var data: Data? {
        if hasAlpha {
            return UIImagePNGRepresentation(self)
        } else {
            return UIImageJPEGRepresentation(self, 1.0)
        }
    }

}

のように UIImage を拡張し、画像フォーマットを切り替えてあげると便利です。もちろん用途によりますが。

Splatoon2を快適にプレイする為にambieを買った。

買いました。

f:id:FromAtom:20170801104531j:plain

発売当初にほしかったんだけど、在庫切れが続いてそのまま忘れていた。

月日は流れ、Splatoon2が発売された。会社のイカ勢とプラベやバイトをすることが多いのだが、Splatoonはゲーム音声を聞いてないと戦況が分かりにくい。また射撃音で武器の種類や方向、距離感も分かるので、音は結構重要になっている。一方で、「右から来てるで!」「自陣に1人入り込んでる!」など、チームメンバーと口頭でコミュニケーションをする必要性もある。普通のイヤホンやヘッドホンだと耳が塞がれてしまうので、片耳だけつけて運用していたけど方向が分からなくて難しかった。

もちろんボイチャ機能を使うこともできるが、物理的に近い場所にいるので少し不便。(ちなみに、自宅参戦勢はDiscordを使っている。)

そこでambieである。このイヤホンの話をすると「骨伝導?」とみんな勘違いするけど、骨伝導ではない。上記写真でオレンジ色の穴が確認できると思うけど、そこから音が出る。雑に言うと、耳に挟んで使うスピーカーみたいなもの。完全に耳を塞がないので、外の音も聞こえて便利な代物である。

ambie.co.jp

以下、実際に使ってみての感想。

  • ゲーム音声もちゃんと聞こえるし会話もできてめっちゃ捗る!
  • つけるの難しいけど2,3回やると慣れる
  • 音をでかくするともちろん音漏れする
  • 会話できる程度の音量にすると音漏れしないしちょうどいい
  • イヤーピース絶対なくすので持ち運び時はケース必須
  • 低音は出ないけどゲームなら特に問題ない
    • 集中してやる時はBOSE使ってる

という感じ。ちょっと高いけどいい買い物だった。ちなみに普段の仕事中は Coffitivity を流すのに使っている。

Swiftで透明なUIImageを生成する

毎回忘れるのでメモ

private func toumeiImage(size: CGSize) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
    guard let context = UIGraphicsGetCurrentContext() else {
        return nil
    }

    // 背景を透明で塗りつぶす
    context.setFillColor(UIColor.clear.cgColor)
    let rect = CGRect(origin: .zero, size: size)
    context.fill(rect)

    // 画像に変換する
    let toumeiImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    guard let image = toumeiImage else {
        return nil
    }

    return image
}

結婚しました。

f:id:FromAtom:20170628090522j:plain

4月に結婚してました。相手はデザイナーの一般女性です。この指輪は鎌倉彫金工房というお店で手作りしました。また今度、体験記を書こうと思います。

これはうちの愛猫のしじみで、特徴はかわいいことです。

f:id:FromAtom:20170628203320j:plain

結婚した翌週に私の祖父が亡くなり、慶弔が重ね合わせ状態となりドタバタしていたら6月になってました。冠婚葬祭は重なりますね。

これはうちの愛猫のしじみで、最近手を伸ばして寝ます。痺れないのか不思議です。

f:id:FromAtom:20170628211539j:plain

嫁とは2012年から付き合い始めたので、かれこれ5年付き合ったことになります。長いですね。

これはうちの愛猫のしじみで、嫁に懐いており僕には厳しい。よく手を噛まれる。

f:id:FromAtom:20161020085805j:plain

そんなわけで、例のアレです。物をもらうと人間は喜びます。よろしくお願いします。

amzn.asia

知人・友人・同僚の皆様各位、これからも夫婦・しじみ共々よろしくお願いいたします。

社内勉強会でSwiftのOptionalについて話しました。

SwiftのOptionalは1度分かると簡単に扱えるけど、分かるまでが難しい。 新卒にiOSアプリ開発を教える機会があって、Optionalもさらっと教えようと思ったんだけど、 まったくさらっと教えられなかったので、丁寧に資料を作ることにした。

speakerdeck.com

僕自身はノリと雰囲気でコードを書いているので、 こうやって自分の理解を体系立ててまとめるのはいい勉強になった。

SwiftでCollectionTypeのindexをなめたい時はindicesが便利

こういうコードを書いてた。

for index in 0..<array.count {    
    // indexを使った処理
}

indices ってのがCollectionTypeに生えていて、それをつかえば良いと知った。

for index in array.indices {    
    // indexを使った処理
}

HHKB Pro2とMacBook Proで擬似セパレートキーボードし始めた。

※この記事は疑似セパレートキーボードで書かれています。

f:id:FromAtom:20170522190523j:plain

こんな感じ。

元ネタはここ。

hatenanews.com

前からErgoDox Ezが欲しいんだけど、値段が値段だけに躊躇していた。そもそもホームポジションもへったくれもない打ち方をしていたので、セパレート式のキーボードでちゃんと打てる自信が無かった。 都合よく手元にはHHKB Pro2とMBPというキーボードが2台あったので試してみることにした。

やってみた感想としては、

  • 半日ぐらいで慣れて普通に打てる
  • 楽な姿勢でタイピングできてとても楽
  • SierraでKarabiner死んで不安だったけど、Karabiner-Elementsを入れればちゃんとCtrl等の同時押しも認識された
  • キーボードの高さやキータッチが違うけど案外なんとかなる

という感じ。つらい点としては

  • けっこう場所をとる
  • CtrlやEnterの位置は同じなのでちょっと手首がグネる

という感じ。とはいえ案外使いやすくて開発速度も遅くならなかったので、ErgoDoxが買えるまでこのまま使っていこうと思う。