SwiftUI Share - ShareLink for All Versions
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:
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)
}
}
}
}