Sharing files in SwiftUI

A new CoreTransferable framework makes it easy to share data in SwiftUI with ShareLink.

In this article, we will be talking about sharing files using ShareLink, while preserving their name and extension. Let's look at how to share a PDF file.

Before we start

First, we need an actual file as a property of our view, and data that will populate our PDF.

Don't forget to import PDFKit!

@State private var pdfDocument: PDFDocument = PDFDocument()
@State private var data: Data = Data()
@State private var previewImage: Image = Image("")
@State private var filename: String

Here, we are defining properies of our view that will be required for populating the ShareLink. But, there's one more thing we need to implement before we can populate them!

Implementing a preview image for our PDF

Since a ShareLink can optionally take a preview image that will be shown on a share sheet, we need to write an extension for the PDFDocument type:

extension PDFDocument {
  public var imageRepresenation: UIImage? {
    guard let pdfPage = self.page(at: 0) else { return nil }
    let pageBounds = pdfPage.bounds(for: .cropBox)

    let renderer = UIGraphicsImageRenderer(size: pageBounds.size)
    let image = renderer.image { ctx in
      UIColor.white.set()
      ctx.fill(pageBounds)

      ctx.cgContext.translateBy(x: 0.0, y: pageBounds.size.height)
      ctx.cgContext.scaleBy(x: 1.0, y: -1.0)

      UIGraphicsPushContext(ctx.cgContext)
      pdfPage.draw(with: .cropBox, to: ctx.cgContext)
      UIGraphicsPopContext()
    }
    return image
  }
}

This will create and render an image presentation for any instance of a PDFDocument.

Now, we have an image that we can pass to the ShareLink!

Creating the PDF

In our view's .onAppear modifier, we can create our file and its image like so:

.onAppear {
  guard let pdf = PDFDocument(data: data),
  let image = pdf.imageRepresenation else {
    fatalError("something went wrong...")
  }

  // Set the name of the file!
  pdf.documentAttributes![PDFDocumentAttribute.titleAttribute] = filename
  self.pdfDocument = pdf
  self.previewImage = Image(uiImage: image)
}

Here, we are populating our view's pdfDocument and previewImage properties to hold the PDF document and its preview image.

The most important part is to set the title attribute to our PDF's name. This is a very important step since this will make the saved file to have the right name.

Getting a PDF's title

As we have seen, it is not really convenient to access a title of a given PDF, because it is stored in its metadata and is of type Optional<Dictionary<AnyHashable, Any>>.

Because of that, let's write another function that will extend the PDFDocument to get its title.

extension PDFDocument {
  public var title: String? {
  guard let attributes = self.documentAttributes,
        let titleAttribute = attributes[PDFDocumentAttribute.titleAttribute]
  else { return nil }

  return titleAttribute as? String
  }
}

This checks if a title attribute exists and converts it to a String.

Conforming to Transferable

Finally, to pass our PDF document to the ShareLink, we need a PDFDocument to conform to the Transferable protocol.

extension PDFDocument: Transferable {
public static var transferRepresentation: some TransferRepresentation {
  FileRepresentation(exportedContentType: .pdf) { pdf in
    guard let data = pdf.dataRepresentation() else {
    fatalError("Could not create a pdf file")
    }

    var fileURL = FileManager.default
      .temporaryDirectory

    if let title = pdf.title {
      fileURL = fileURL
        .appendingPathComponent(title)
    }

    try data.write(to: fileURL)
    return SentTransferredFile(fileURL)
    }
  }
}

Since we want to specifically share as a file, we only need to implement FileRepresentation.

Here, we specify that we need a PDF content type. Then, we create a new file URL and optionally provide a title if it exists. (Here's why we wrote that title extension earlier!) Finally, we write our file to that URL and return it as a SentTransferredFile. This is a return type for a FileRepresentation.

Creating the ShareLink

Finally, we can create a ShareLink in our view and place it in the Toolbar.

.toolbar {
    ToolbarItem(placement: .navigationBarTrailing) {
      ShareLink(item: pdfDocument,
        preview: SharePreview(
          filename,
          image: previewImage
        )
      )
  }
}

This will successfully present a share sheet with a correct name for the PDF and a preview image! Note that once you save it, the name of the file will be preserved, too!

Thank you for reading, and I hope you enjoyed this article! 💛

Tagged with: