Sharing content with ShareLink: best practices explained
Native sharing in SwiftUI has become a critical part of user engagement, whether you're building social features, allowing users to export data, or simply letting them send content to friends. ShareLink, introduced in iOS 16 and macOS 13, handles this elegantly without the complexity of UIActivityViewController or relying on custom sharing logic. In this article, we'll walk through how to implement ShareLink effectively, handle custom types, and optimize the sharing experience across platforms.
ShareLink for URLs, text and images
ShareLink is a view that wraps any content and provides native system sharing. For simple cases like URLs, text, or images, it works out of the box with minimal configuration. Let's start with the most common scenario: sharing a URL.
import SwiftUI
struct ArticleDetailView: View {
let articleURL: URL
let articleTitle: String
var body: some View {
VStack {
Text(articleTitle)
.font(.title)
ShareLink(
item: articleURL,
subject: Text("Check this out"),
message: Text("I found this interesting article")
) {
Label("Share", systemImage: "square.and.arrow.up")
}
}
}
}This creates a button that, when tapped, opens the system share sheet. The user can then send the URL via Messages, Mail, AirDrop, or any installed sharing extension. The subject and message parameters customize what gets sent—particularly useful for email where the subject line matters.
Sharing text follows the same pattern. In an app where users compose notes or quotes, you might expose the ability to share them:
struct QuoteView: View {
let quoteText: String
let author: String
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("\"\(quoteText)\"")
.font(.body)
.italic()
Text("— \(author)")
.font(.caption)
.foregroundColor(.secondary)
ShareLink(
item: quoteText,
subject: Text("A quote by \(author)"),
message: Text("Found this on our app")
) {
Label("Share Quote", systemImage: "square.and.arrow.up")
}
}
.padding()
}
}For images, the pattern remains consistent. However, we need to be mindful of performance—loading large images into memory before sharing can block the UI. ShareLink handles this gracefully by deferring the actual data transfer until the user confirms the share action.
struct ImageShareView: View {
let image: UIImage
let imageName: String
var body: some View {
VStack {
Image(uiImage: image)
.resizable()
.scaledToFit()
ShareLink(
item: image,
preview: SharePreview(imageName, image: Image(uiImage: image))
) {
Label("Share Image", systemImage: "square.and.arrow.up")
}
}
}
}Notice here we've introduced the preview parameter. This customizes how the shared content appears in the share sheet and on receiving devices. Without it, the system makes its best guess.
Transferable for custom types
The real power of ShareLink emerges when you need to share custom data structures. This is where the Transferable protocol comes in. By conforming to Transferable, you define how your custom type should be encoded for sharing.
Consider a task management app where users want to share project summaries. Instead of sharing raw JSON or a link, you could allow sharing a formatted summary:
struct ProjectSummary: Codable {
let name: String
let description: String
let taskCount: Int
let dueDate: Date
}
extension ProjectSummary: Transferable {
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation(contentType: .plainText) { summary in
"""
Project: \(summary.name)
\(summary.description)
Tasks: \(summary.taskCount)
Due: \(dateFormatter.string(from: summary.dueDate))
"""
}
}
private static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()
}Now you can share a ProjectSummary directly:
struct ProjectCardView: View {
let project: ProjectSummary
var body: some View {
VStack(alignment: .leading) {
Text(project.name)
.font(.headline)
Text(project.description)
.font(.body)
.foregroundColor(.secondary)
ShareLink(item: project) {
Label("Share Project", systemImage: "square.and.arrow.up")
}
}
}
}For more complex scenarios, use CodableRepresentation to share JSON or DataRepresentation for raw binary data. You can also provide multiple representations—the system will pick the most appropriate one for the destination:
extension ProjectSummary: Transferable {
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation(contentType: .plainText) { summary in
// Plain text version for messaging apps
summary.formattedText
}
CodableRepresentation(contentType: .json) { summary in
// JSON version for email or file-based sharing
summary
}
}
}This multi-representation approach ensures maximum compatibility. A user sharing via Messages gets readable text, while someone forwarding via email gets structured JSON they could paste into a tool.
Preview titles and images
The share sheet preview significantly impacts the user experience. A well-crafted preview makes content immediately recognizable and shareable. SharePreview lets you customize what appears in the system share interface before the user selects a destination.
struct RestaurantCardView: View {
let restaurant: Restaurant
let image: UIImage
var body: some View {
VStack {
Image(uiImage: image)
.resizable()
.scaledToFill()
VStack(alignment: .leading) {
Text(restaurant.name)
.font(.headline)
Text(restaurant.cuisine)
.font(.caption)
.foregroundColor(.secondary)
ShareLink(
item: restaurant.website,
preview: SharePreview(
restaurant.name,
image: Image(uiImage: image)
)
) {
Label("Share", systemImage: "square.and.arrow.up")
}
}
.padding()
}
}
}The preview title should be concise and descriptive. The image should be representative and load quickly. Keep images under 1MB when possible to ensure smooth sharing to all destinations. The preview also works with SharePreview's message parameter:
ShareLink(
item: recipeURL,
subject: Text("Try this recipe"),
message: Text("I made this last night and it was delicious!"),
preview: SharePreview(
recipe.name,
image: Image(uiImage: recipeImage)
)
) {
Label("Share Recipe", systemImage: "square.and.arrow.up")
}When sharing URLs with preview images, the image must be available locally—the system doesn't fetch remote images from the URL's OpenGraph tags. This is intentional for performance and privacy.
Works on iOS and macOS
ShareLink maintains API consistency across iOS and macOS, though the underlying sharing mechanisms differ slightly. On iOS, it uses the native share sheet. On macOS, it may route to Mail, Messages, or other sharing extensions depending on what's available.
The same code works on both platforms without modification:
import SwiftUI
struct ContentView: View {
let documentURL: URL
let documentName: String
var body: some View {
VStack {
Text("Document ready to share")
ShareLink(
item: documentURL,
subject: Text(documentName),
preview: SharePreview(documentName)
) {
Label("Share Document", systemImage: "square.and.arrow.up")
}
}
#if os(macOS)
.padding()
#endif
}
}On macOS, you might want slightly different styling or layout. Use conditional compilation minimally—the default behavior works well on both platforms. One consideration: on macOS, ShareLink may appear in the menu bar or toolbar more naturally than in a VStack.
File-based sharing (like exporting documents) works particularly well cross-platform. Many macOS users expect to export content; ShareLink handles this seamlessly through the share extensions available on their system.
Common mistakes
Developers often make several predictable errors when implementing ShareLink:
- Forgetting to defer heavy operations: If you're generating content on-the-fly (rendering PDFs, resizing images), do this asynchronously or defer until share time. Don't block the UI thread preparing share data.
- Using oversized preview images: A 4000x3000 pixel preview image is unnecessary and slows the share sheet. Provide images at screen resolution, around 400x300 at most.
- Not testing the actual share destinations: Test with Mail, Messages, AirDrop, and Notes to ensure your custom Transferable implementations look right in context.
- Sharing sensitive data without warning: If users might accidentally share private information (like API keys in exports), add explicit confirmation or filtering logic before ShareLink processes the data.
- Assuming the Transferable protocol handles all conversions: For complex types, you may need to transform data into simpler formats (text, JSON) explicitly. Don't rely on Codable alone.
- Ignoring platform differences in file handling: macOS can share files directly; iOS may need to copy to temporary locations first. Test both.
Key takeaways
- ShareLink handles URLs, text, and images natively without boilerplate code—use it as your default sharing mechanism.
- Implement Transferable for custom types to define how they encode for different destinations (text, JSON, binary).
- Customize the share sheet appearance with SharePreview using concise titles and appropriately-sized preview images.
- The same ShareLink code works on iOS and macOS; test both platforms to ensure consistency in user experience.
- Avoid blocking the UI when preparing share content; defer heavy operations until the user confirms the share action.
- Provide multiple transfer representations when appropriate so different destinations receive data in their optimal format.
- Always validate and transform data before sharing to prevent accidental exposure of sensitive information.






Comments
Loading comments…