Reading time: 3 min

This recipe shows how to share in SwiftUI. It showcases the new SwiftUI 4 ShareLink, as well as providing a drop-in replacement compatible with all SwiftUI versions that internally relies on UIActivityViewController.

The end result looks like this:

preview

Sharing with ShareLink

The easiest way to share content in SwiftUI is with ShareLink, which is a view that controls sharing presentation. It's essentially a button that, upon pressing, presents a share sheet.

ShareLink is available starting in SwiftUI 4 (iOS 16, macOS 12.4).

You can get started just by providing a sharing item (or multiple of them):

struct ShareTest: View {
  let link = URL(string: "https://swiftuirecipes.com")!

  var body: some View {
    ShareLink(item: link)
  }
}

You can also customize just the title or the entire label:

ShareLink("Best SwiftUI recipes!", item: link)
ShareLink(item: link) {
  Text("Share share share")
    .foregroundColor(.red)
}

You can also provide subject and message that can be forwarded to some sharing targets:

ShareLink(item: link, subject: Text("Subject"), message: Text("Message"))

Lastly, you can also specify a custom sharing preview:

ShareLink(item: link, preview: SharePreview("SwiftUIRecipes.com", image: Image("icon")))
Sharing on all versions - ShareLinkCompat

In order to support sharing on earlier SwiftUI versions, we have to resort to good old UIKIt's UIActivityViewController, which we'll wrap in UIViewControllerRepresentable. Here's the code for that:

struct UIKitActivityView: UIViewControllerRepresentable {
  @Binding var isPresented: Bool

  let data: [Any]
  let subject: String?
  let message: String?

  func makeUIViewController(context: Context) -> UIViewController {
    HolderViewController(control: self)
  }

  func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
    let activityViewController = UIActivityViewController(
      activityItems: data,
      applicationActivities: nil
    )

    if isPresented && uiViewController.presentedViewController == nil {
      uiViewController.present(activityViewController, animated: true)
    }

    activityViewController.completionWithItemsHandler = { (_, _, _, _) in
      isPresented = false
    }
  }

  class HolderViewController: UIViewController, UIActivityItemSource {
    private let control: UIKitActivityView

    init(control: UIKitActivityView) {
      self.control = control
      super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
      fatalError("init(coder:) has not been implemented")
    }

    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
      control.message ?? ""
    }

    func activityViewController(_ activityViewController: UIActivityViewController,
                                itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
      control.message
    }

    func activityViewController(_ activityViewController: UIActivityViewController,
                                subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
      control.subject ?? ""
    }
  }
}

Next, we can sketch a version of ShareLink that will work on any SwiftUI version. It will capture most of ShareLink's interface and functionality, while internally delegating to UIKitActivityView:

@available(iOS 13.0, macOS 10.15, *)
struct ShareLinkCompat<Item, Label>: View where Item : Transferable, Label : View {
  let item: Item
  let subject: String?
  let message: String?
  @ViewBuilder let label: () -> Label

  @State private var isPresented = false

  init(item: Item,
       subject: String? = nil,
       message: String? = nil,
       @ViewBuilder label: @escaping () -> Label) {
    self.item = item
    self.subject = subject
    self.message = message
    self.label = label
  }

  var body: some View {
    Button(action: {
      isPresented = true
    }, label: label)
    .background(UIKitActivityView(isPresented: $isPresented,
                                  data: [item, subject ?? ""],
                                  subject: subject,
                                  message: message))
  }
}

Now, we can take it a step further and provide a default label, just like ShareLink does:

struct DefaultShareLinkLabelCompat: View {
  let title: String?

  var body: some View {
    HStack {
      Image(systemName: "square.and.arrow.up")
      Text(title ?? "Share")
    }
  }
}

extension ShareLinkCompat where Label == DefaultShareLinkLabelCompat {
  init(_ title: String? = nil,
       item: Item,
       subject: String? = nil,
       message: String? = nil) {
    self.init(item: item,
              subject: subject,
              message: message,
              label: { DefaultShareLinkLabelCompat(title: title) })
  }
}

Finally, here's how you can put it to use:

struct ShareLinkCompatTest: View {
  let link = URL(string: "https://swiftuirecipes.com")!

  var body: some View {
    VStack(spacing: 20) {
      ShareLinkCompat(item: link, subject: "Subject", message: "Message")
      ShareLinkCompat("Best SwiftUI recipes!", item: link)
      ShareLinkCompat(item: link) {
        Text("Share share share")
          .foregroundColor(.red)
      }
    }
  }
}

Next Post Previous Post