- Published on
Detect Successful Share Sheet Completion in SwiftUI
- Authors

- Name
- Mick MacCallum
- @0x7fs
SwiftUI's ShareLink view is the preferred way to present a share sheet, but it has a significant limitation: there's no way to know whether the user actually completed sharing. This can be problematic if you want to reward users for sharing your app, track analytics, unlock features conditionally, or trigger actions based on successful shares.
Some developers attempt to work around this by using simultaneousGesture() to detect taps on ShareLink, but this approach only tells you that the button was tapped, not whether the user followed through with sharing or simply cancelled the sheet. To get true completion feedback and know definitively whether content was shared, you need to bridge to UIKit's UIActivityViewController, which provides a robust completion handler with detailed information about the sharing outcome.
Wrapping UIActivityViewController
We can create a UIViewControllerRepresentable wrapper that exposes the completion handler from UIActivityViewController:
struct ActivityViewControllerView: UIViewControllerRepresentable {
var activityItems: [Any]
var applicationActivities: [UIActivity]? = nil
var completionWithItemsHandler: UIActivityViewController.CompletionWithItemsHandler?
func makeUIViewController(
context: UIViewControllerRepresentableContext<ActivityViewControllerView>
) -> UIActivityViewController {
let controller = UIActivityViewController(
activityItems: activityItems,
applicationActivities: applicationActivities
)
controller.completionWithItemsHandler = { activityType, completed, returnedItems, error in
self.completionWithItemsHandler?(activityType, completed, returnedItems, error)
}
return controller
}
func updateUIViewController(
_ uiViewController: UIActivityViewController,
context: UIViewControllerRepresentableContext<ActivityViewControllerView>
) {}
}
Creating a ShareButton Component
To make this easier to use, we can create a reusable ShareButton view that handles the presentation and provides a cleaner API:
enum ShareButtonResult {
case shared(UIActivity.ActivityType?)
case cancelled
case failed(Error)
}
struct ShareButton<Label>: View where Label: View {
var activityItems: [Any]
var applicationActivities: [UIActivity]? = nil
var completion: @MainActor (ShareButtonResult) -> Void
@ViewBuilder var label: () -> Label
@State private var isSharePresented = false
var body: some View {
Button {
isSharePresented = true
} label: {
label()
}
.sheet(isPresented: $isSharePresented) {
ActivityViewControllerView(
activityItems: activityItems,
applicationActivities: applicationActivities
) { activityType, completed, _, error in
if let error {
completion(.failed(error))
} else if completed {
completion(.shared(activityType))
} else {
completion(.cancelled)
}
}
}
}
}
Note that it's unusual for a SwiftUI view to use a completion handler rather than state binding. This is a pragmatic workaround since we're bridging to UIKit's UIActivityViewController, which itself uses completion handlers.
Understanding the Completion Parameters
The UIActivityViewController.CompletionWithItemsHandler provides four parameters that help you understand what happened:
- activityType: An optional
UIActivity.ActivityTypeindicating which activity was used (e.g.,.message,.mail,.copyToPasteboard,.postToFacebook). This isnilif the user cancelled. - completed: A Boolean indicating whether the activity was completed successfully.
- returnedItems: Optional items returned by the activity. Most activities don't return items, so this is often
nil. - error: An optional error if the activity failed.
By examining the activityType, you can track which sharing methods users prefer, helping you optimize your app's sharing features or understand user behavior patterns.
Using the ShareButton
Here's how to use the ShareButton in your app:
struct ContentView: View {
@State private var shareCount = 0
var body: some View {
VStack(spacing: 20) {
Text("Shares: \(shareCount)")
.font(.headline)
ShareButton(
activityItems: ["Check out this awesome app!"],
completion: { result in
switch result {
case .shared(let activityType):
shareCount += 1
print("Shared via: \(activityType?.rawValue ?? "unknown")")
case .cancelled:
print("User cancelled sharing")
case .failed(let error):
print("Share failed: \(error.localizedDescription)")
}
},
label: {
Label("Share App", systemImage: "square.and.arrow.up")
}
)
}
.padding()
}
}
With this approach, you can track successful shares, reward users, update analytics, or trigger any other action based on whether the user actually completed the share action.
Continue Learning
Adding an App Delegate to a SwiftUI App
Learn how to integrate UIKit's App Delegate into your SwiftUI app for handling app lifecycle events and system callbacks.
Preventing Screenshot Capture in SwiftUI Views
Learn how to prevent users from taking screenshots of sensitive content in your SwiftUI app using field-level security and UIKit bridging for financial, medical, or private data protection.
Implementing Pull-to-Refresh Without List in SwiftUI
Learn how to add pull-to-refresh functionality to custom ScrollViews in SwiftUI without using List, perfect for custom layouts and complex scroll content.
