SwiftUI ShareLink set file name

1.8k views Asked by At

I'm using the ShareLink to share an FileDocument which contains a String. The FileDocument is conform to the Transferable protocol.

This is the FileDocument Struct:

struct TransferableDocument: FileDocument, Transferable {

  static var transferRepresentation: some TransferRepresentation
  {
      DataRepresentation(exportedContentType: .text) { log in
          log.convertToData()
      }
  }

  // tell the system to support only text
  static var readableContentTypes: [UTType] = [.text]

  // by default the document is empty
  var text = ""

  // this initializer creates a empty document
  init(initialText: String = "") {
      text = initialText
  }

  // this initializer loads data that has been saved previously
  init(configuration: ReadConfiguration) throws {
      if let data = configuration.file.regularFileContents {
          text = String(decoding: data, as: UTF8.self)
      }
  }

  // this will be called when the system wants to write the data to disk
  func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
      let data = Data(text.utf8)
      return FileWrapper(regularFileWithContents: data)
  }

  func convertToData() -> Data
  {
      return text.data(using: .ascii) ?? Data()
  }
}

And this is the ShareLink:

var doc: TransferableDocument
{
    return TransferableDocument(initialText: "I'm a String")
}

ShareLink(item: doc ,preview: SharePreview("logfile")) 
{
    Text("Share")
}

When using AirDrop, the filename is set to the SharePreview title, in this case "logfile". When sharing it to Apps like Mail, the filename is simply set to "text".

Is there any way to set a default filename?

4

There are 4 answers

0
jan On

This is now working, starting from iOS 17:

extension MyDocument: Transferable {
    public static var transferRepresentation: some TransferRepresentation {
        DataRepresentation(contentType: .foo) { document in
           [...]
        } importing: { data in
           [...]
        }
        .suggestedFileName { document in
            document.bar
        }
    }
}

1
Jannik Arndt On

Here's a solution that allows to set the filename dynamically from the content of the object you want to share (in this case a SwiftData class):

import CoreTransferable
import SwiftData

@Model
class Session: Codable, Transferable {
    var startTime: Date? = Date.now

    // conform to Codable
    enum CodingKeys: String, CodingKey { ... }
    required init(from decoder: Decoder) throws { ... }
    func encode(to encoder: Encoder) throws { ... }

    static var transferRepresentation: some TransferRepresentation {
        let rep = DataRepresentation<Session>(exportedContentType: .json) { session in
            let encoder = JSONEncoder()
            encoder.dateEncodingStrategy = .iso8601
            return try! encoder.encode(session)
        }

        return rep.suggestedFileName { session in session.suggestedFileName }
    }

    var suggestedFileName: String { "Session on \(startTime!.ISO8601Format()).json" }

Then you can simply use

ShareLink(
    item: session,
    preview: SharePreview(session.suggestedFileName)
)

and the "preview name" in the share sheet AND the actual file name is set.

The trick is to explicitly set the type of the DataRepresentation<Session>.

0
Bugmeister On

We had a similar issue and added an additional FileRepresentation to the transferRepresentation func with the appropriate file name configured when saving out the data. With that in place when shared to Mail the appropriate file name was used.

static var transferRepresentation: some TransferRepresentation
{
    DataRepresentation(exportedContentType: .text) { log in
        log.convertToData()
    }

    FileRepresentation(exportedContentType: .text) { log in
        let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent("logfile").appendingPathExtension("txt")

        try log.convertToData().write(to: fileURL)

        return SentTransferredFile(fileURL)
    }
}
4
feca On

There is a method: suggestedFileName

DataRepresentation(exportedContentType: .png) { layer in
    layer.pngData()
}
.suggestedFileName("Layer.png")

Update1:

It is compiling now with Xcode 14.3 with FileRepresentation. But I cant share a file with FileRepresentation on iOS 16.0 Simulator so I don't know it is working or not on iOS 16.0, however it works on iOS 16.1 Simpulator. On macOS there is no "Save to File" option in sharing popup so I don't know it is working or not. :(

I mentioned a bug with exportCondition in comments, and it is working as expected on iOS 16.4 and macOS 13.3.

Update 2:

The Transferable protocol still not working as expected yet.

  1. If you have multiple FileRepresentation and one of them has a suggestedFileName, that will be use with every FileRepresentation. Even when the exportingCondition of FileRepresentation with suggestedFileName returns false.
  2. exportingCondition cannot be used with Main Actor isolated object.

I think the suggestedFileName is also useless because cannot determine with transferable object (example a document name).