LoginSignup
0
2

SwiftUIレイアウトで複数ページのPDFを作成する

Last updated at Posted at 2023-09-12

前回の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の初期化が走っていたようだ。
また、次に試していて解ったのがdrawHierarchyafterScreenUpdates:trueで行うことでも初期化が実施されているようだ。
ただ非同期を外すと、何故かコンテキストには書き込まれない。これも不思議だ。

結果的に、loadViewdrawHierarchylayer.renderを順に実行することでコンテキストに初期化したViewが書き込めることが判明した。

残った疑問としては、

  • drawHierarchylayer.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が実行となった。
また、loadViewdrawHierarchyを行わなくても書き込めた。
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()
}

修正したコードがこちら

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2