swiftuitransferableswiftui-sharelinkcore-transferable

ShareLink with multiple items only shares one at a time


I have a String and an URL containing audio and want to share both via a Sharelink.

I have written a Transferable:

struct TransferableTextAndAudio: Codable, Transferable {
  let text: String
  let audio: URL
  
  static var transferRepresentation: some TransferRepresentation {
    ProxyRepresentation(exporting: \.text)
    FileRepresentation(exportedContentType: .audio) { item in
      return SentTransferredFile(item.audio, allowAccessingOriginalFile: true)
    }
  }
}

and I share with:

ShareLink(item: TransferableTextAndAudio(text: "String to share", audio: audioUrl),
          preview: SharePreview("Preview")) {
  Label("Share text and audio", systemImage: "square.and.arrow.up")
}

However, only the text is shared. When I comment out the ProxyRepresentation line, then the audio is shared. Why is that ? Do I miss some understanding how Transferable works ?


Solution

  • Sharing one transferable item having multiple representations is still just sharing one item. Only one representation will be picked. Since you put the text representation first, that is the one that will be prioritised.

    You need to use one of the ShareLink initialisers that take multiple items, like this one. Share the text and audio file as two separate items. The two items annoyingly needs to be of the same type. You can write an enum with two cases like this:

    enum TextOrAudio: Codable, Transferable {
        case text(String)
        case audio(URL)
        
        var text: String? {
            if case let .text(s) = self {
                s
            } else {
                nil
            }
        }
        
        var audio: URL? {
            if case let .audio(url) = self {
                url
            } else {
                nil
            }
        }
        
        static var transferRepresentation: some TransferRepresentation {
            FileRepresentation(exportedContentType: .audio) { item in
                return SentTransferredFile(item.audio!, allowAccessingOriginalFile: false)
            }
            .exportingCondition { $0.audio != nil }
            ProxyRepresentation(exporting: \.text!)
                .exportingCondition { $0.text != nil }
        }
    }
    

    The exportingConditions make sure that the correct representation is used for each enum case.

    Then you can write:

    ShareLink(
        items: [
            TextOrAudio.audio(someURL),
            TextOrAudio.text("Some Text")
        ]
    ) { _ in
        SharePreview("Preview")
    } label: {
        Label("Share text and audio", systemImage: "square.and.arrow.up")
    }
    

    Mail.app will put the text in the email body and attach the audio file, but ultimately it is up to the receiving app what to do with these two items.