markdown
はじめに (対象読者・この記事でわかること)
この記事は、iOS アプリ開発に携わる iPhone/iPad アプリ開発者、特に Swift と PencilKit を使用して手書き入力やイラスト描画機能を実装したい方を対象としています。
読者は以下のことが理解でき、実装できるようになります。
- PencilKit のインクが期待したほど鮮明でない(解像度が低い)と感じたときの根本原因
- 描画キャンバスのスケールや DPI 設定、デバイス座標系の扱い方
- 高解像度インクを実現するためのコード変更とベストプラクティス
背景として、iPad Pro の Apple Pencil を用いたアプリで「線が細くてぼやける」ケースが増えており、ユーザー体験を損なうことが報告されています。本記事はその課題を技術的に掘り下げ、実装例と共に解決策を提示します。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
- Swift の基本文法と Xcode の基本的な操作
- UIKit/Foundation の基本概念、特に UIView のライフサイクル
- PencilKit の基本的な使い方(PKCanvasView の設置と PKTool の選択)
PencilKit のインク品質が低下する原因と概要
PencilKit は内部で CoreGraphics のベクターベース描画エンジンを使用していますが、アプリ側でキャンバスやビューのスケーリングが適切でないと、実際の描画解像度がデバイスのピクセル密度に対して低くなります。主な原因は次の 3 点です。
-
UIView の
contentScaleFactorがデバイスのスケールと合っていない
iOS デバイスは Retina ディスプレイで 2x または 3x のスケールを持ちますが、ビューのcontentScaleFactorが 1.0 のままだと、描画はローレゾのバッファに落とし込まれます。 -
PKCanvasView の
drawingPolicyが.anyInputではなく.default
.defaultはデバイスの座標系に最適化されますが、スケーリングを考慮しないため、Apple Pencil の高精細なサンプリング情報が失われがちです。 -
PKCanvasViewを Auto Layout で制約した際のサイズ変更タイミング
ビューのサイズが変わった瞬間に内部バッファが再生成されますが、再生成時にスケールが正しく設定されていないと、以降の描画が低解像度になります。
上記の問題は、実装のほんの小さなミスでも顕在化し、ユーザーは「線がぼやける」「細部が失われる」ように感じます。次のセクションでは、これらの原因を一つずつ検証し、コードレベルでの対策を示します。
高解像度インクを実現する具体的な手順と実装
以下では、実際の Xcode プロジェクトを例に、PencilKit のインク品質を向上させるためのステップバイステップの実装方法を解説します。
ステップ 1: viewDidLoad で contentScaleFactor をデバイススケールに合わせる
Swiftoverride func viewDidLoad() { super.viewDidLoad() // PKCanvasView を生成して自動レイアウトに設定 let canvasView = PKCanvasView() canvasView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(canvasView) NSLayoutConstraint.activate([ canvasView.leadingAnchor.constraint(equalTo: view.leadingAnchor), canvasView.trailingAnchor.constraint(equalTo: view.trailingAnchor), canvasView.topAnchor.constraint(equalTo: view.topAnchor), canvasView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) // ここがポイント:デバイスのスクリーンスケールで contentScaleFactor を強制設定 canvasView.contentScaleFactor = UIScreen.main.scale self.canvasView = canvasView // プロパティに保持しておく }
UIScreen.main.scaleは 2.0 または 3.0 を返し、デバイスのピクセル密度に合わせます。- これにより、内部のビットマップバッファが Retina 解像度で確保され、インクの線が細部までクリアに描画されます。
ステップ 2: drawingPolicy を .anyInput に変更し、座標変換を自前で行う
SwiftcanvasView.drawingPolicy = .anyInput // 入力座標をそのまま受け取る
.anyInput にすると、PencilKit は Apple Pencil からの高精度サンプル情報(時間・圧力・傾き)をそのまま返すので、スケールをプログラム側で意識的に調整しやすくなります。その後、ビューのサイズが変わった際に座標系の変換を行うハンドラを追加します。
Swiftoverride func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() adjustCanvasScale() } private func adjustCanvasScale() { guard let canvas = canvasView else { return } let viewSize = canvas.bounds.size let scale = UIScreen.main.scale // 1ポイント=scaleピクセルとして描画領域を再計算 let scaledSize = CGSize(width: viewSize.width * scale, height: viewSize.height * scale) canvas.drawing = canvas.drawing.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) canvas.bounds = CGRect(origin: .zero, size: viewSize) canvas.sizeThatFits(scaledSize) // 必要に応じて内部バッファをリサイズ }
viewDidLayoutSubviewsは Auto Layout がレイアウトを完了した直後に呼ばれるため、ここでスケールをリセットすればサイズ変更に伴う解像度低下を防げます。
ステップ 3: PKTool の設定で線幅と筆圧感度を最適化
Swiftlet ink = PKInk(.pen, color: .black) // ペンインクの基本設定 let tool = PKInkingTool(ink, width: 5.0, opacity: 1.0) // 幅と不透明度を明示 canvasView.tool = tool
- 幅
widthはポイント単位で指定しますが、contentScaleFactorが 2x/3x になると実際のピクセル幅は自動で拡大されます。 opacityを 1.0 にすると、圧力情報がそのままアルファに反映され、薄い線が濃くなる問題が緩和されます。
ステップ 4: PKCanvasView の drawing を保存・復元するときの DPI 設定
PencilKit の PKDrawing は内部で PDF のようなベクトルデータを保持していますが、エクスポートや保存時に画像に変換する場合、解像度がデフォルトの 72 DPI に落ちることがあります。高解像度画像として保存する例です。
Swiftfunc exportDrawingAsPNG() -> Data? { guard let drawing = canvasView.drawing else { return nil } let scale = UIScreen.main.scale let image = drawing.image(from: canvasView.bounds, scale: scale) // 高 DPI で描画 return image.pngData() }
scaleパラメータにUIScreen.main.scaleを渡すだけで、PNG 画像はデバイスのピクセル密度に合わせたサイズになります。- これにより、保存した画像を他のプラットフォームで表示しても線がぼやけません。
ハマった点やエラー解決
1. contentScaleFactor を設定しても解像度が変わらない
- 原因:
PKCanvasViewがまだレイアウトされていない段階でcontentScaleFactorを設定した。 - 解決策:
viewDidLayoutSubviewsまたはviewWillAppearでcanvasView.boundsが確定した後に設定する。
2. .anyInput に変更したら座標が予期せずずれる
- 原因: ビューのサイズ変更時にスケール変換を忘れていた。
- 解決策:
adjustCanvasScaleメソッドでdrawingをCGAffineTransformによってスケーリングし、boundsも再設定する。
3. PNG エクスポート時に画像が小さくなる
- 原因:
image(from:scale:)のscaleに 1.0 を渡していた。 - 解決策:
UIScreen.main.scale(2.0/3.0)を渡すように変更し、Retina ディスプレイに合わせたピクセル数で画像を生成した。
まとめ
本記事では、Swift の PencilKit においてインクがぼやける問題の根本原因と、高解像度描画を実現するための 4 つの具体的手順(contentScaleFactor の設定、drawingPolicy の変更、PKTool の最適化、エクスポート時の DPI 設定)を詳しく解説しました。
- 解像度低下はビューのスケール設定が不適切なことが主因
contentScaleFactorとdrawingPolicyの組み合わせで高精細入力を取得- 保存時のスケール指定で画像も高品質を維持
この手順を実装すれば、Apple Pencil の細かい筆圧・傾き情報をフルに活かしたクリアな線を描くことが可能です。今後は、リアルタイムエフェクトやレイヤー合成といった応用的なトピックにも挑戦していきます。
参考資料
- Apple Developer Documentation – PencilKit
- WWDC 2023 – Advances in PencilKit
- 「iOS UI Programming」 (著: 小林 祥平) – UIKit と CoreGraphics の連携解説ページ
- Swift.org – Swift Language Guide
