前回のPDFKitでPDFを読み込んで結合した際に透過部分が黒塗りになる現象が発生することがわかった。
どうやらPDFのページ追加時にcontext.cgContext.clear(rect)
で毎回コンテキストをクリアする必要があるようだが、PDFKitで読み込んだ場合のコンテキストの操作がわからないので諦めた。
なのでPDF作成時に複数ページを書き込んでから保存するのが妥当と判断。修正したコードがこちら。
これでSwiftUIで複数ページのPDFが安定して作成できる筈だ。
前回のPDFKitのページ結合時の透過現象の解決方法はいつか解る筈。。。
修正(2023/09/25)
メインスレッドのTask内でPDFコンテキストにrootViewController
でレンダリングすると表示中のViewがPDFと混ざることが確認できた。
なのでrootViewController
を用いたやり方は実用的とは言えないので没とした。
色々試した結果Task内で最新のViewの内容をdrawHierarchy
でコンテキストに書き出せることが判明した。
次に、何故Task内であればdrawHierarchy
でコンテキストに書き込めるのか?
これも試していてようやく解った。
UIViewControllerの初期化ができていないのだ。
Taskに書いたことによって、非同期のタイミングでViewの初期化が走っていたようだ。
また、次に試していて解ったのがdrawHierarchy
をafterScreenUpdates:true
で行うことでも初期化が実施されているようだ。
ただ非同期を外すと、何故かコンテキストには書き込まれない。これも不思議だ。
結果的に、loadView
、drawHierarchy
、layer.render
を順に実行することでコンテキストに初期化したViewが書き込めることが判明した。
残った疑問としては、
-
drawHierarchy
、layer.render
の順で実行することで、Viewの初期化とコンテキストの書き込みが何故成功するのか?- 要素の整理
-
loadView
: Viewを読み込む -
drawHierarchy
: 初期化後のViewに更新(この時何故か書き込まない) -
layer.render
: コンテキストにViewを書き込む
-
- 要素の整理
- Task内であれば、
drawHierarchy
だけでコンテキストが何故書き込めるのか?- 非同期のタイミングで初期化。仕様通りコンテキストの書き込みも行う
つまり非同期でない時にdrawHierarchy
がコンテキストに書き込まないのがバグと考えるのが自然?
非同期の時はviewDidLoad
が自動で走るようにレンダリングも行われると考えるのが自然か。わからんけど。カスタムクラスでも作って動作を見るのが妥当かな。
追及してみる
createPdfでviewsに渡す値をUIViewControllerに変更してテストしてみた。
class ViewController: UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
print("init")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
print("viewDidLoad Start")
super.viewDidLoad()
view.backgroundColor = .white
let label = UILabel(frame: CGRectMake(0, 0, 100, 100))
label.text = "Page1"
view.addSubview(label)
print("viewDidLoad End")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear")
}
}
func _createPdf(_ vc: UIViewController, rect: CGRect,
context: UIGraphicsPDFRendererContext){
print("==1")
vc.view.frame = rect
print("==2")
context.beginPage()
context.cgContext.clear(rect)
print("==3")
vc.view.layer.render(in: context.cgContext)
print("==4")
vc.removeFromParent()
vc.view.removeFromSuperview()
}
結果はframeでサイズを設定した時点でviewDidLoadが実行となった。
また、loadView
、drawHierarchy
を行わなくても書き込めた。
UIViewControllerの挙動としては本来このように動くのが正しいのだろう。
init
==1
viewDidLoad Start
viewDidLoad End
==2
==3
==4
戻って、元のコードでdrawHierarchy
を実行した場合には下記のメッセージが出ていた。
2023-09-22 08:00:51.668624+0900 PDFExample[3659:71578] [Snapshotting] View (0x13b51bb80, _TtGC7SwiftUI14_UIHostingViewVS_7AnyView_) drawing with afterScreenUpdates:YES inside CoreAnimation commit is not supported.
警告が出るということは何か間違えているのだろうということで、snapshotView
を用いたところメッセージは出なくなった。
また、snapshotView
を実行するとloadView
も不要ということがわかった。
snapshotviewのドキュメントによると「ぼかしなどのグラフィカルエフェクトを適用」したい場合はdrawHierarchy
をコールするのが良いと書かれているので単にViewを初期化したいのであればsnapshotview
を呼ぶのが正解なのだろう。
以上から修正内容。
drawHierarchy
を使わないのでコンテキストに何故書き込まないのか悩むこともなくなりスッキリした。
Before
func _createPdf(_ view: AnyView, rect: CGRect,
context: UIGraphicsPDFRendererContext){
let vc = UIHostingController(rootView: view)
vc.view.frame = rect
vc.loadView()
context.beginPage()
context.cgContext.clear(rect)
vc.view.drawHierarchy(in: rect, afterScreenUpdates: true)
vc.view.layer.render(in: context.cgContext)
vc.removeFromParent()
vc.view.removeFromSuperview()
}
After
func _createPdf(_ view: AnyView, rect: CGRect,
context: UIGraphicsPDFRendererContext){
let vc = UIHostingController(rootView: view)
vc.view.frame = rect
vc.view.snapshotView(afterScreenUpdates: true)
context.beginPage()
context.cgContext.clear(rect)
vc.view.layer.render(in: context.cgContext)
vc.removeFromParent()
vc.view.removeFromSuperview()
}
修正したコードがこちら