7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

はじめに

初投稿です.
今回は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

取得した範囲を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

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

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

image04.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()
    }
}

7
6
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
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?