能登 要

札幌在住のiOSアプリ開発者。SwiftUI により分割されたデバイス間を縦横にやりとりできる考え方に転換しています。

iOSアプリ開発者。2009年のiPhoneアプリ開発開始時期から活動。開発言語のアップデートの中でSwiftUIおよび周辺技術に着目中。

チュートリアルで教えてくれないベクター描画用強力ツールPaintCode - SwiftUI

端的にいうと

ベクターツールなのにソースコードを生成するPaintCodeとSwiftUIでの利用方法についての記事。SwiftUIのShapeでPaintCodeを活用する方法。

1) Pathを使うのが辛い

SwiftUI TutorialsはApple謹製のSwiftUIチュートリアルでありSwiftUIでアプリを開発するにあたって重要点を網羅している。デザインについては一家言あるAppleらしくベクター描画だけで1講義を割いている。

Drawing Paths and Shapes — SwiftUI Tutorials | Apple Developer Documentation

ここで紹介されているPathを使用するとSwiftUIコード上でベクター描画のためのシェイプを作成できる。 基本図形であればそれで問題ないが、キャラクターのアイコンなど複雑なベクター描画のために点および制御点をコードでプロットしていくのは正直きつい。

まるでデザインツールで描く様にベクター描画をプロットできないものか?

それを実現するアプリが存在する。その名はPaintCodeである。

PaintCodeIcon

2) PaintCode

ベクターツールなのにソースコードを生成するPaintCodeは有料アプリで現在のバージョンは3.x、アプリとしてはSwiftUI以前からあるアプリである(2021年2月現在)。特徴は描画した描画をソースコードをの形で出力する。Apple向けにはiOS, macOSに対応する。

PaintCode - Turn your drawings into Objective-C or Swift drawing code

2-1) 特徴

描画内容にリアルタイムでソースコードが反映される。そのままコピー&ペーストすることが可能。後述にエクスポート機能で描画コンポーネントとしてソースコード出力も可能。動的なパラメータ入力にも対応している。

2-2) データ形式

プロジェクトの保存形式は独自形式となっている。

2-3) インポート

SVG形式の一部インポートに対応している。

2-4) エクスポート

描画オブジェクトをStyleKitという名称でコンポーネントとして出力することも可能となっている。描画オブジェクトをPNG画像ファイル、PDF形式で出力も可能となっている。

PaintCodeMainWindow

3) SwiftUIのShape

SwiftUIのPathは、初期値としてiOS、macOSのベジェパスオブジェクトを渡すことができる。iOS、macOSのベジェパスオブジェクトはそれぞれ、UIBezierPath、NSBezierPathを渡すことができる特性を利用する。

環境それぞれのジェベパスオブジェクトを渡すのであれば処理が作業が増えるだけであるがiPhone/iPad用のアプリだけを対象にするのであればUIBezierPathを渡すだけで良い。

SwiftUI TutorialsではPathの使用方法について解説していたが、パスを描画するサイズをGeometoryReader経由で取得していた。カスタムShape(Shape)も使用すると、描画療育を引数として渡す関数path()を利用できる。

func path(rect: ) -> Path

path(rect:)の引数rectはShapeを描画する領域を取得できる。またrectを使用して描画領域を調整することも可能。描画領域を考慮しない場合はShape中央に描画オブジェクトが配置される。

4) PaintCode使用の注意点

PaintCodeはSwiftUI以前のツールのため(そしてSiwftUI未対応)のため仕様の際には数点修正が必要となる。修正はPaintCode側での修正と、SwiftUI側での修正となる。

4-1) PaintCode側での修正

PaintCodeでは円や矩形(角丸矩形含む)、直線、曲線についてはOSごとの最適された機能が呼び出すためあえてBezier形式に変換する必要がある。PaintCode上で図形を選択 - Convert To Bezierを選択するとBezier形式に変換が完了する。

HowToPaintCodeConvertBezier

PaintCodeではシェイプの重ね合わせは描画の上書きとなるがSwiftUIのShapeでは上書き描画されない。対策としては、

  • PaintCode内で図形を統合(Union)する
  • 図形要素それぞれにShapeを作成する

PaintCode内で図形を統合すると、線描(stroke)と塗り(fill)は共有されてしまう。別の線描と塗りを指定場合は別シェイプに分ける必要がある。

HowToPaintCodeUnion

表示確認時に色指定を忘れるとPaintCodeから取り込んだシェイプが有効確認できないので仮の色をわりあてておく。SwiftUIのforegroundColorモデファイヤーにColor(Color.red, Color.blueなど)を割り当てるようにする。

4-2) SiwftUI側での修正

UIBezierPathをSwiftUIのPathに変換する必要がある。Pathは引数としてCGPathを渡すことで初期化できる。

return Path( cgPath: bezier.cgPath() )

変数bezierは、PaintCodeでオブジェクトを切り貼りしていると変わってくるので適宜変数名を調整する。

SwiftUI側での基本的な修正はここまでとなる。

以下からはPainCode側でFrame機能を使った場合の対処法について記述する。

PaintCodeのFrame機能について簡単に説明すると、UIKitのAutoSizing機能とほぼ同意の機能となっている。UIKitであれば揃えはViewオブジェクトだけだが、PaintCodeでは描画オブジェクトや描画オブジェクト中の点、つまりベジェパス中の点を操作できる。

SwiftUIにも移植可能だがSwiftUIのShapeのrectパラメータに相当する変数がframeとなっているのでリネームするかframe変数をrectで初期化する。以下は変数名を追加した場合のコードとなっている。

let frame = rect /// 以下からPaintCodeで生成されるコードを使用可能

5) サンプル

以前の投稿に実際に利用したコードがあるので確認して欲しい。

SwiftUIを使った可動表現(MechanicalArm) - SwiftUI100行チャレンジ⑤ | Irimasu Densan Planning - いります電算企画

6) まとめ

SwiftUIでPathをプロットする代替手段としてPaintCodeの使い方を紹介してみたが利用には余計な手順がかかっているのが現状である。

PaintCodeの開発元のPixelCutは真っ先にSwiftUIに対応してほしかったが今はモックアップアプリに注力しているようでPaintCodeのSwiftUI対応の声は聞こえてこない。今後Xcode上でベクター描画を編集できる可能性も開発元は考慮にいれてアップデートは控えている可能性もある。

個人的にはPaintCodeはシンプルなベクター編集ソフトとしても愛用しているので私としては今後アップデートに期待したい。