【SwiftUI】ViewのスクリーンショットとPDF出力

#はじめに
初投稿です．
今回はSwiftUIで，指定した範囲の画面をスクリーンショットしてPDF出力を行い，ファイル内に保存する機能をまとめています．

~やること~
■画面のスクリーンショット機能の実装
■スクリーンショットのデータをPDFとして保存する機能の実装

#CGRectによるPDF取得範囲の指定
はじめにスクリーンショットしたい範囲の選択を行います．
取得したサイズと同じサイズの透明な幕を作るイメージです．
この透明な幕が今回取得するサイズになります．

```ContentView.swift
struct RectangleGetter: View {
    @Binding var rect: CGRect

    var body: some View {
        GeometryReader { geometry in
            self.createView(proxy: geometry)
        }
    }

    func createView(proxy: GeometryProxy) -> some View {
        DispatchQueue.main.async {
            self.rect = proxy.frame(in: .global)
        }
        return Rectangle().fill(Color.clear)
    }
}
```

#使い方
CGRectの変数にゼロを格納しておきます．

```ContentView.swift
@State private var rect: CGRect = .zero
```

今回HStackで囲まれた範囲を取得します．

```ContentView.swift
@State private var rect: CGRect = .zero![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359686/010a5f4c-8b56-37b3-7662-4a52a4f9798d.png)
HStack {
                    Image(systemName: "camera")
                        .font(.title)
                        .foregroundColor(.white)
                    Text("Hello, World!?")
                        .font(.title)
                        .foregroundColor(.white)
                }
                .padding()
                .frame(width:400,height:200)
                .background(Color.yellow)
                .cornerRadius(8)
        .background(RectangleGetter(rect: $rect))

```
取得イメージ
![image01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359686/2dc50323-4474-6867-3d6d-a9615d635fd6.png)

#取得した範囲をUIImageに変換

SwiftUIのViewをUIImageに変換します．

```ContentView.swift
extension UIView {
    func getImage(rect: CGRect) -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: rect)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}
```

UIImageを格納する変数を作成しておきます．

```ContentView.swift
@State var uiImage: UIImage? = nil
```

CGRectで取得した範囲をUIImageに変換します．


```ContentView.swift
self.uiImage = UIApplication.shared.windows[0].rootViewController?.view!.getImage(rect: self.rect)
```
![image02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359686/7999d687-e5d4-43a3-2c75-8cdc18142c99.png)

#UIImageをPDFとして一時保存
変換したUIImageをPDFとして保存します．また保存時拡張子にPDFを選択します．
本来このfuncでは引数にUIViewを用いますが今回は取得しているのがUIImageなのでUIImageViewを用いてます．
(UIImageは使えない)

```ContentView.swift
func createPdfFromView(hosting: UIImageView, saveToDocumentsWithFileName fileName: String) {
    let pdfData = NSMutableData()
    UIGraphicsBeginPDFContextToData(pdfData, hosting.bounds, nil)
    UIGraphicsBeginPDFPage()
    guard let pdfContext = UIGraphicsGetCurrentContext() else { return }
    hosting.layer.render(in: pdfContext)
    UIGraphicsEndPDFContext()
    if let documentDirectories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
        let documentsFileName = documentDirectories + "/" + fileName + ".pdf"
        pdfData.write(toFile: documentsFileName, atomically: true)
    }
}
```
変換したUIImageをPDFとして保存する。この際、”PDF名前”には自身が保存する際のファイル名を入力します．
※「PDF名前」は保存する際にデフォルトで値が入ります．

```ContentView.swift
createPdfFromView(hosting:UIImageView(image:uiImage),saveToDocumentsWithFileName:"PDF名前")
```
#PDFが一時保存されているディレクトリからフルパスを取得します．

PDFが一時保存されている場所からフルパスを取得します．

```ContentView.swift
func fileSave(fileName: String) -> URL {
    let dir = FileManager.default.urls( for: .documentDirectory, in: .userDomainMask ).first!
    let filePath = dir.appendingPathComponent(fileName, isDirectory: false);
    return filePath
```

自身で指定した”PDF名前”に拡張子がついた”PDF名前.pdf”をfileNameに格納します．

```ContentView.swift
 var  url  = fileSave(fileName: "PDF名前.pdf")
```

#PDF出力ボタンを押した際に画面下部から表示されるアクティブシート

ファイルへ保存する際下から表示されるアクティブシートを作成する。activetyItems;[Any]に保存する対象を入力します．

```ContentView.swift
struct ActivityView: UIViewControllerRepresentable {

    let activityItems: [Any]
    let applicationActivities: [UIActivity]?
    
    func makeUIViewController(
        context: UIViewControllerRepresentableContext<ActivityView>
    ) -> UIActivityViewController {
        return UIActivityViewController(
            activityItems: activityItems,
            applicationActivities: applicationActivities
        )
    }

    func updateUIViewController(
        _ uiViewController: UIActivityViewController,
        context: UIViewControllerRepresentableContext<ActivityView>
    ) {
        // Nothing to do
    }
}
```

今回url変数に格納先を指定しているため[Any]にはurlを入れます．

```ContentView.swift
.sheet(isPresented: self.$showActivityView) {
   ActivityView(activityItems: [url],applicationActivities: nil)
}
```

![image03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359686/d2bca048-ca3f-c883-d7f0-a8b9e615de56.png)

以下のように，ファイルに保存できるようになります．

![image04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359686/23ea7974-6b88-8f47-9ffa-3cbcdf124b61.png)

完成です．

全体のコードは以下の通りです．

```ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var rect: CGRect = .zero
    @State var uiImage: UIImage? = nil
    @State private var showActivityView: Bool = false
    var body: some View {
        var url = fileSave(fileName: "PDF名前.pdf")
        VStack {
            HStack {
                Image(systemName: "camera")
                    .font(.title)
                    .foregroundColor(.white)
                Text("Hello, World!?")
                    .font(.title)
                    .foregroundColor(.white)
            }
            .padding()
            .frame(width: 400, height: 200)
            .background(Color.yellow)
            .cornerRadius(8)
            .background(RectangleGetter(rect: $rect))

            Button(action: {
                self.showActivityView.toggle()
                self.uiImage = UIApplication.shared.windows[0].rootViewController?.view!.getImage(rect: self.rect)
                createPdfFromView(hosting: UIImageView(image: uiImage), saveToDocumentsWithFileName: "PDF名前")
            }) {
                Text("PDF出力")
                    .padding()
                    .foregroundColor(Color.white)
                    .background(Color.red)
                    .cornerRadius(8)

            }.sheet(isPresented: self.$showActivityView) {
                ActivityView(
                    activityItems: [url],

                    applicationActivities: nil
                )
            }
        }
    }
}

// ----------------------------------------
struct RectangleGetter: View {
    @Binding var rect: CGRect

    var body: some View {
        GeometryReader { geometry in
            self.createView(proxy: geometry)
        }
    }

    func createView(proxy: GeometryProxy) -> some View {
        DispatchQueue.main.async {
            self.rect = proxy.frame(in: .global)
        }
        return Rectangle().fill(Color.clear)
    }
}

// ------------------------------------------
extension UIView {
    func getImage(rect: CGRect) -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: rect)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

func createPdfFromView(hosting: UIImageView, saveToDocumentsWithFileName fileName: String) {
    let pdfData = NSMutableData()
    UIGraphicsBeginPDFContextToData(pdfData, hosting.bounds, nil)
    UIGraphicsBeginPDFPage()
    guard let pdfContext = UIGraphicsGetCurrentContext() else { return }
    hosting.layer.render(in: pdfContext)
    UIGraphicsEndPDFContext()
    if let documentDirectories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
        let documentsFileName = documentDirectories + "/" + fileName + ".pdf"
        pdfData.write(toFile: documentsFileName, atomically: true)
    }
}

struct ActivityView: UIViewControllerRepresentable {
    let activityItems: [Any]
    let applicationActivities: [UIActivity]?

    func makeUIViewController(
        context: UIViewControllerRepresentableContext<ActivityView>
    ) -> UIActivityViewController {
        return UIActivityViewController(
            activityItems: activityItems,
            applicationActivities: applicationActivities
        )
    }

    func updateUIViewController(
        _ uiViewController: UIActivityViewController,
        context: UIViewControllerRepresentableContext<ActivityView>
    ) {
        // Nothing to do
    }
}

func fileSave(fileName: String) -> URL {
    let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    let filePath = dir.appendingPathComponent(fileName, isDirectory: false)
    return filePath
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

```