SwiftでCocoaTouchFrameworkを開発していて、特に不便だなと思うのは、 ImageLiteralが使用出来ないということです。
これは、ImageLiteralが、mainのBudleからリソース取得しようとするため、使用すると、mainのBundleで画像が無いとクラッシュしてしまうためです。
しかし、Xcodeの機能上、Frameworkのプロジェクト上でもImageLiteralは使えますし、コンパイルも通るので、使えないのはなんとももどかしいところです。
なので、今回は、そこをなんとかハックして、CocoaTouchFramework上でImageLiteralを使用する方法を見つけたので、紹介します。
#_ExpressibleByImageLiteral
_ExpressibleByImageLiteralというProtocolを利用すれば、Framework上でImageLiteralを使用することができます。
ただし、このProtocolはXcodeの予測変換で出てこないので、実際に打ち込む必要があるので気をつけましょう。
とりあえず簡単に実装してみると以下のようになります。
// _ExpressibleByImageLiteralは予測変換で出てこないImageLiteralのProtocol
struct FrameworkImageLiteral: _ExpressibleByImageLiteral {
let image: UIImage
// pathにはImageLiteralで指定したImageの名前が入ってくる
init(imageLiteralResourceName path: String) {
// 通常のImageLiteralの動作と同じように初期化できなかったらアプリを落とすために!にする
self.image = UIImage(named: path, in: Bundle.current, compatibleWith: nil)!
}
}
Bundle.currentに関しては、以前に以下の記事を書いたので、それを参考にしてください。
##実際に使ってみるとこんな感じ
let image = (#imageLiteral(resourceName: "image_name") as FrameworkImageLiteral).image
これでFramework上のBundleからImageLiteralを使用して画像を取得できます。
###なんかイケてなくね?
(#imageLiteral(resourceName: "image_name") as FrameworkImageLiteral)
しかし、キャストするのが少し鬱陶しいので、もう少しいい感じにする方法を考えてみました。
###いい感じにしてみた。
まずは最初の_ExpressibleByImageLiteralを実装した構造体でUIImageを生成せずに、Pathだけ保持するように変更します。
struct FrameworkImageLiteral: _ExpressibleByImageLiteral {
let path: String
init(imageLiteralResourceName path: String) {
// ここでImageを生成せずにpathだけ保持しておく
self.path = path
}
}
次にUIImageのextensionで、Frameworkimageliteralを引数にして、先ほどFrameworkImageLiteralで行なっていたUIImageの初期化をこちらで行います。
extension UIImage {
convenience init!(literal: FrameworkImageLiteral) {
self.init(named: literal.path, in: Bundle.current, compatibleWith: nil)!
}
}
それで実際に使用するとこんな感じになります。
let image = UIImage(literal: #imageLiteral(resourceName: "image_name"))
変にキャストが挟まらないので、いい感じになりました。