bekoneggu525
@bekoneggu525

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Pencilkitでpdfに描画

解決したいこと

Swiftuiでpdfを表示させ、表示したpdfにpencilkitを用いて、手書きで描画するアプリを作っています。
pdfを表示させる前は、pencilkitの機能は正常に作動し、機能の切り替え(ペンの色の変更や消しゴム機能への変更など)も問題なくできるのですが、pdfを表示後に描画を行うと機能の切り替えが正常に認知しなくなります。
解決方法を教えて下さい。

発生している問題・エラー

IMG_0003.jpg

上記画像のように、pdfを表示させる前に選択していた、赤色のペンから変更ができません。マーカーや消しゴムを選んでも、赤色のペンが表示される状態です。

該当するソースコード

import SwiftUI
import PDFKit
import PencilKit
import UniformTypeIdentifiers

struct PDFViewContainer: UIViewRepresentable {
    @Binding var pdfDocument: PDFDocument?

    func makeUIView(context: Context) -> PDFView {
        let pdfView = PDFView()
        //pdfの拡大率を調整
        pdfView.autoScales = true
        //表示モード
        pdfView.displayMode = .singlePage
        return pdfView
    }

    func updateUIView(_ uiView: PDFView, context: Context) {
        uiView.document = pdfDocument
    }
}


struct PKCanvasViewRepresentable: UIViewRepresentable {
    @Binding var drawing: PKDrawing
    @Binding var selectedTool: PKTool
    @Binding var toolPickerVisible: Bool
    
    var pkcView: PKCanvasView
    //let pkcView = PKCanvasView()
    typealias UIViewType = PKCanvasView
    let toolPicker = PKToolPicker()
    
    func makeUIView(context: Context) -> PKCanvasView {
        configureCanvasView(pkcView)
        return pkcView
    }
    
    func updateUIView(_ uiView: PKCanvasView, context: Context) {
        //uiView.drawing = drawing
        //configureCanvasView(uiView)
    }
    
    func configureCanvasView(_ canvasView: PKCanvasView) {
        canvasView.drawingPolicy = PKCanvasViewDrawingPolicy.anyInput
        //canvasView.tool = selectedTool
        toolPicker.addObserver(canvasView) //変化を検知
        toolPicker.setVisible(true, forFirstResponder: canvasView) //表示
        canvasView.becomeFirstResponder() //変化を適応させる
                if !toolPickerVisible {
                    // ツールピッカーが非表示の場合、手書き内容を設定
                    canvasView.becomeFirstResponder()
                }
        canvasView.isOpaque = false
    }
}

struct ContentView: View {
    @State private var pdfDocument: PDFDocument?
    @State private var isDocumentPickerPresented = false
    @State private var drawing = PKDrawing()
    @State private var toolPickerVisible = false
    @State private var selectedTool: PKTool = PKInkingTool(.pen, color: .red, width: 5)
    @State private var canvasView = PKCanvasView()

    var body: some View {
        
        ZStack {
                    // PDFViewを追加
                    PDFViewContainer(pdfDocument: $pdfDocument)
                        .frame(maxWidth: .infinity, maxHeight: .infinity)

                    // PKCanvasViewを重ねて描画する
                    PKCanvasViewRepresentable(drawing: $drawing, selectedTool: $selectedTool, toolPickerVisible: $toolPickerVisible, pkcView: canvasView)
        }

        VStack {
                    Button("PDFファイルを選択") {
                    isDocumentPickerPresented = true
                    }
                    .padding()
                }
        
        .fileImporter(
            isPresented: $isDocumentPickerPresented,
            allowedContentTypes: [UTType.pdf],
            onCompletion: { result in
                switch result {
                case .success(let url):
                    
                    //ファイルへのアクセス許可
                    guard url.startAccessingSecurityScopedResource() else {
                        // Handle the failure here.
                        print("startAccessingSecurityScopedResource error")
                        return
                    }
                    
                    // FileManagerを使用してファイルが存在するか確認
                    let fileManager = FileManager.default
                    if fileManager.fileExists(atPath: url.path) {
                        print("ファイルが存在します: \(url.path)")
                    } else {
                        print("ファイルが存在しません: \(url.path)")
                    }
                    
                    //  PDFDocumentの作成
                    if let document = PDFDocument(url: url) {
                        print("PDFドキュメントが正常に読み込まれました")
                        self.pdfDocument = document
                    } else {
                        let error = NSError(domain: "com.example.PDFApp", code: 1, userInfo: [NSLocalizedDescriptionKey: "PDFドキュメントの読み込みに失敗しました"])
                        print("エラー: \(error.localizedDescription), \(error.localizedFailureReason ?? "")")
                    }
                    url.stopAccessingSecurityScopedResource()
                case .failure(let error):
                    print("ファイル選択時にエラーが発生しました:\(error.localizedDescription)")
                }
            }
        )
    }
}

自分で試したこと

PKCanvasViewRepresentableにおける、updateUIViewにmakeUIViewと同様の処理を追加しましたが、ペンや消しゴムを選択するパレットが消えてしまう不具合が起こりました。

1

3Answer

おそらくですが、PKCanvasViewが再作成されていることが原因だと思います。
対策はstatic変数とすることで、PKCanvasViewのインスタンスは変わらないようにします。ただし、そのままだとPDF読み込み後にパレット(PKToolPicker)が消えてしまうので、都度、becomeFirstResponder()するようにしています。
綺麗とは言えないですが、期待通りの動きになっていると思います。

import SwiftUI
import PDFKit
import PencilKit
import UniformTypeIdentifiers

struct PDFViewContainer: UIViewRepresentable {
    @Binding var pdfDocument: PDFDocument?
    
    func makeUIView(context: Context) -> PDFView {
        let pdfView = PDFView()
        pdfView.autoScales = true //pdfの拡大率を調整
        pdfView.displayMode = .singlePage //表示モード
        return pdfView
    }
    
    func updateUIView(_ uiView: PDFView, context: Context) {
        uiView.document = pdfDocument
    }
}


struct PKCanvasViewRepresentable: UIViewRepresentable {
    
    private static var pkcView = PKCanvasView()
    private static var toolPicker = PKToolPicker()
    
    typealias UIViewType = PKCanvasView
    
    func makeUIView(context: Context) -> PKCanvasView {
        configureCanvasView(PKCanvasViewRepresentable.pkcView)
        return PKCanvasViewRepresentable.pkcView
    }
    
    func updateUIView(_ uiView: PKCanvasView, context: Context) {
    }
    
    func configureCanvasView(_ canvasView: PKCanvasView) {
        canvasView.drawingPolicy = .anyInput
        PKCanvasViewRepresentable.toolPicker.addObserver(canvasView) //変化を検知
        PKCanvasViewRepresentable.toolPicker.setVisible(true, forFirstResponder: canvasView) //表示
        canvasView.becomeFirstResponder() //変化を適応させる
        canvasView.isOpaque = false
    }
    
    static func becomeFirstResponder() {
        PKCanvasViewRepresentable.pkcView.becomeFirstResponder()
    }
    
}

struct ContentView: View {
    @State private var pdfDocument: PDFDocument?
    @State private var isDocumentPickerPresented = false
    
    var body: some View {
        
        ZStack {
            // PDFViewを追加
            PDFViewContainer(pdfDocument: $pdfDocument)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            
            // PKCanvasViewを重ねて描画する
            PKCanvasViewRepresentable()
        }
        
        VStack {
            Button("PDFファイルを選択") {
                isDocumentPickerPresented = true
            }
            .padding()
        }
        .onChange(of: pdfDocument) {
            PKCanvasViewRepresentable.becomeFirstResponder()
        }
        .fileImporter(
            isPresented: $isDocumentPickerPresented,
            allowedContentTypes: [UTType.pdf],
            onCompletion: { result in
                switch result {
                    case .success(let url):
                        
                        //ファイルへのアクセス許可
                        guard url.startAccessingSecurityScopedResource() else {
                            // Handle the failure here.
                            print("startAccessingSecurityScopedResource error")
                            return
                        }
                        
                        //  PDFDocumentの作成
                        if let document = PDFDocument(url: url) {
                            print("PDFドキュメントが正常に読み込まれました")
                            self.pdfDocument = document
                        } else {
                            let error = NSError(domain: "com.example.PDFApp", code: 1, userInfo: [NSLocalizedDescriptionKey: "PDFドキュメントの読み込みに失敗しました"])
                            print("エラー: \(error.localizedDescription), \(error.localizedFailureReason ?? "")")
                        }
                        url.stopAccessingSecurityScopedResource()
                    case .failure(let error):
                        print("ファイル選択時にエラーが発生しました:\(error.localizedDescription)")
                }
            }
        )
    }
}
1Like

回答いただきありがとうございます。ご指摘いただいたコードを実行したのですが、pdfを表示させるとパレットが消えてしまいました。@nak435様の環境では、pdfを表示した後もパレットは表示されていますか?

0Like

Comments

  1. @nak435様の環境では、pdfを表示した後もパレットは表示されていますか?

    先ほど上のコードを全部取り込んでiPadシミュレータで確認しましたが、ちゃんと表示しました。
    念の為、再度、上のコードを全部取り込んでみてください。


    当方の環境も付記しておきます。

    • M1 Mac mini, 2020

    • macOS Sonoma 14.2

    • Xcode 15.1

    • Swift 5.9.2

    • Target iOS 17.2

    • Simulator 6th Gen iPad Pro iPadOS 17.2

先ほど実行し直したところ、パレットが表示されるようになりました。回答いただきありがとうございました。

0Like

Comments

Your answer might help someone💌