BLOGサブスレッドの日常
2021.02.18
Xcode12/SwiftUIでSVGを使う
tama
tamaです。
SwiftUIいいですね!コードだけで画面を作れるのがとてもよいです。
そこはかとなくReactで関数コンポーネントを使って画面を作っていくのに似た印象を持っています。
そんなステキなSwiftUIですが、SVGを使うのにちょっとハマったので解決策を残しておきます。
Xcodeは(たぶん12から)SVGファイルを直接Assetsとして持つことができるようになりました。
以前はPDFに変換して持たせていたようですがその必要がなくなっています。
iOSのバージョンも13以降でしか対応していないようですが、SwiftUIを使う場合はiOS13以上のはず。
SVGをAssetsに組み込む方法
- Xcode 12 でAssets.xcassets に Image Set を追加します。名前はお好みで。
- プロパティを次の通り設定します。
- Resizing の
Preserve Vector Data
にチェックを入れる - Scales を
Single Scale
にする
- Resizing の
- All の点線の枠に表示したいSVGファイルをドロップします。
キレイに拡大して表示する方法
SwiftUIの Image ではどうがんばってもキレイに表示できませんでした、、(方法を見つけられていないだけかもしれない)
SwiftUIを使う方の頭には「困ったときの UIViewControllerRepresentable」という魔法の言葉があるかと思います。
今回もそれで解決します。
struct SVGImage: UIViewControllerRepresentable {
let name: String
func makeCoordinator() -> Coordinator {
Coordinator(name: name)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<SVGImage>) -> UIViewController {
context.coordinator
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<SVGImage>) {
}
class Coordinator: UIViewController {
let name: String
init(name: String) {
self.name = name
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
view = UIImageView(image: UIImage(named: name))
}
}
}
UIImage()でリソースからロードしたSVGを UIImageView、UIViewController、UIViewControllerRepresentable を用いて表示するだけのViewです。
SVGImage(name: "logo.image")
.frame(width: 500, height: 200)
のように呼んでやればキレイに拡大縮小することができます。
任意の色でSVGをレンダリングする方法
SF Symbolsのようにコードで色を指定してSVGイメージを描画することもできます。
SwiftUI の Image を使う場合はこんな感じです。
Image("logo.image")
.resizable()
.renderingMode(.template)
.foregroundColor(.green)
.frame(width: 500, height: 200)
.renderingMode(.template)
するかわりに Assetsのプロパティで Render As を Template Image
にする方法もあります。
(Assetsのプロパティで指定するともともとSVGが持っていた色情報が使われなくなります)
キレイに拡大縮小しながら色も指定したい、、という欲張りさんにはこちら。
struct SVGImage: UIViewControllerRepresentable {
let controller: Coordinator
init(name: String) {
controller = Coordinator(name: name)
}
func makeCoordinator() -> Coordinator {
controller
}
func makeUIViewController(context: UIViewControllerRepresentableContext<SVGImage>) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<SVGImage>) {
uiViewController.view = controller.imageView
}
func scaledToFill() -> Self {
controller.contentMode(.scaleAspectFill)
return self
}
func scaledToFit() -> Self {
controller.contentMode(.scaleAspectFit)
return self
}
func renderingMode(_ renderingMode: Image.TemplateRenderingMode?) -> Self {
switch renderingMode {
case .original:
controller.renderingMode(.alwaysOriginal)
case .template:
controller.renderingMode(.alwaysTemplate)
default:
controller.renderingMode(.automatic)
}
return self
}
func imageColor(_ color: Color) -> Self {
imageColor(color.uiColor())
}
func imageColor(_ color: UIColor) -> Self {
controller.imageColor(color)
return self
}
class Coordinator {
let name: String
let imageView = UIImageView()
init(name: String) {
self.name = name
imageView.image = UIImage(named: name)
}
func contentMode(_ mode: UIView.ContentMode) {
imageView.contentMode = mode
}
func renderingMode(_ renderingMode: UIImage.RenderingMode) {
imageView.image = imageView.image?.withRenderingMode(renderingMode)
}
func imageColor(_ color: UIColor) {
imageView.tintColor = color
renderingMode(.alwaysTemplate)
}
}
}
(2020/02/19 コードを修正しました)
SwiftUI の Color を UIColor に変換する方法はこのあたり(1,2)を参考にしてください。
(私は Color.blue のように名前で指定されたときにちゃんと UIColor.systemBlue にマップされるようにswitch文を途中に入れました)
SVGImage(name: "logo.image")
.imageColor(.blue)
.frame(width: 500, height: 200)
みたくすることでキレイに拡大縮小しながら色も指定することができます。
つまり、今回の結論は「困ったときの UIViewControllerRepresentable」
この記事を書いた人
tama