Showing 'UIActivityViewController' in SwiftUI
Asked Answered
C

16

80

I want to let the user to be able to share a location but I don't know how to show UIActivityViewController in SwiftUI.

Cordiacordial answered 10/6, 2019 at 21:8 Comment(0)
C
92

The basic implementation of UIActivityViewController in SwiftUI is

import UIKit
import SwiftUI

struct ActivityViewController: UIViewControllerRepresentable {

    var activityItems: [Any]
    var applicationActivities: [UIActivity]? = nil

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
        let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
        return controller
    }

    func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {}

}

And here is how to use it.

struct MyView: View {

    @State private var isSharePresented: Bool = false

    var body: some View {
        Button("Share app") {
            self.isSharePresented = true
        }
        .sheet(isPresented: $isSharePresented, onDismiss: {
            print("Dismiss")
        }, content: {
            ActivityViewController(activityItems: [URL(string: "https://www.apple.com")!])
        })
    }
}
Cumshaw answered 11/10, 2019 at 13:8 Comment(5)
I have the image link to share, how would it be in that case? It works with UIImage (named:" Product ") but as it is with an Image (" Product ") in SwiftUI. To SHOW the image in my view, I use ImageViewContainer1 (imageUrl: self.item.image! .Url), but it doesn't accuse me to put thatShied
In case someone willing to share the app link, the URL is "apps.apple.com/us/app/id(your app id)"Cymophane
This will crash on iPads due to popoverPresentationController not being set.Abirritate
@CalebFriden could you provide some details about the crash? I tried it on iPadOS 14.2 in a simulator and on a physical device (iPad Pro 11" 2018) - it didn't crash for me.Catchascatchcan
I've just tried it in iPad simulator and it works perfectly.Flynn
L
25

Based on Tikhonov's, the following code added a fix to make sure the activity sheet is dismissed properly (if not subsequently the sheet will not be presented).

struct ActivityViewController: UIViewControllerRepresentable {

    var activityItems: [Any]
    var applicationActivities: [UIActivity]? = nil
    @Environment(\.presentationMode) var presentationMode

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
        let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
        controller.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
            self.presentationMode.wrappedValue.dismiss()
        }
        return controller
    }

    func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {}

}
Lasko answered 9/2, 2020 at 15:4 Comment(1)
Since presentationMode is deprecated, you can use @Environment(\.dismiss) private var dismissAction and call it self.dismissAction()Wondawonder
F
17

It's a one time thing currently. .sheet will show it as a sheet, but bringing it up again from the same view will have stale data. Those subsequent shows of the sheet will also not trigger any completion handlers. Basically, makeUIViewController is called only once which is the only way to get the data to share into the UIActivityViewController. updateUIViewController has no way to update the data in your activityItems or reset the controller because those are not visible from an instance of UIActivityViewController.

Note that it doesn't work with UIActivityItemSource or UIActivityItemProvider either. Using those is even worse. The placeholder value doesn't show.

I hacked around some more and decided that maybe the problem with my solution was a sheet that was presenting another sheet, and when one went away then the other stayed.

This indirect way of having a ViewController do the presentation when it appears made it work for me.

class UIActivityViewControllerHost: UIViewController {
    var message = ""
    var completionWithItemsHandler: UIActivityViewController.CompletionWithItemsHandler? = nil
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        share()
    }
    
    func share() {
        // set up activity view controller
        let textToShare = [ message ]
        let activityViewController = UIActivityViewController(activityItems: textToShare, applicationActivities: nil)

        activityViewController.completionWithItemsHandler = completionWithItemsHandler
        activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash

        // present the view controller
        self.present(activityViewController, animated: true, completion: nil)
    }
}

struct ActivityViewController: UIViewControllerRepresentable {
    @Binding var text: String
    @Binding var showing: Bool
    
    func makeUIViewController(context: Context) -> UIActivityViewControllerHost {
        // Create the host and setup the conditions for destroying it
        let result = UIActivityViewControllerHost()
        
        result.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
            // To indicate to the hosting view this should be "dismissed"
            self.showing = false
        }
        
        return result
    }
    
    func updateUIViewController(_ uiViewController: UIActivityViewControllerHost, context: Context) {
        // Update the text in the hosting controller
        uiViewController.message = text
    }
    
}

struct ContentView: View {
    @State private var showSheet = false
    @State private var message = "a message"
    
    var body: some View {
        VStack {
            TextField("what to share", text: $message)
            
            Button("Hello World") {
                self.showSheet = true
            }
            
            if showSheet {
                ActivityViewController(text: $message, showing: $showSheet)
                    .frame(width: 0, height: 0)
            }
            
            Spacer()
        }
        .padding()
    }
}
Farmland answered 27/8, 2019 at 18:27 Comment(1)
viewDidAppear must call superBastien
C
15

May be its not recommended, but it is really easy and two line of code (was for iPhone) to share text

Button(action: {
      let shareActivity = UIActivityViewController(activityItems: ["Text To Share"], applicationActivities: nil)
      if let vc = UIApplication.shared.windows.first?.rootViewController{
          shareActivity.popoverPresentationController?.sourceView = vc.view
         //Setup share activity position on screen on bottom center
          shareActivity.popoverPresentationController?.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height, width: 0, height: 0)
          shareActivity.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.down
         vc.present(shareActivity, animated: true, completion: nil)
      }
}) {
    Text("Share")
}

EDIT: Now works fine on iPad (tested on iPad Pro (9.7 -inch) Simulator)

iOS 15

        Button(action: {
            guard let vc = UIApplication.shared.connectedScenes.compactMap({$0 as? UIWindowScene}).first?.windows.first?.rootViewController else{
                return
            }
            let shareActivity = UIActivityViewController(activityItems: ["Text To Share"], applicationActivities: nil)
            shareActivity.popoverPresentationController?.sourceView = vc.view
            shareActivity.popoverPresentationController?.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height, width: 0, height: 0)
            shareActivity.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.down
            vc.present(shareActivity, animated: true, completion: nil)
        }) {
            Text("Share")
        }
Clareclarence answered 23/11, 2020 at 5:28 Comment(3)
This works in iPhone but crashes on iPad, any idea how I can make this work on iPad, too?Studious
@Studious It crashes because on iPad it does not know where the sourceView is https://mcmap.net/q/99362/-uiactivityviewcontroller-crashing-on-ios-8-ipadsLowbred
There's a compiler warning UIApplication.shared.windows was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead. Can you please update this answer?Foppery
O
8

I want to suggest another implementation that looks more native (half screen height without white gap bottom).

import SwiftUI

struct ActivityView: UIViewControllerRepresentable {
    var activityItems: [Any]
    var applicationActivities: [UIActivity]? = nil
    @Binding var isPresented: Bool

    func makeUIViewController(context: Context) -> ActivityViewWrapper {
        ActivityViewWrapper(activityItems: activityItems, applicationActivities: applicationActivities, isPresented: $isPresented)
    }

    func updateUIViewController(_ uiViewController: ActivityViewWrapper, context: Context) {
        uiViewController.isPresented = $isPresented
        uiViewController.updateState()
    }
}

class ActivityViewWrapper: UIViewController {
    var activityItems: [Any]
    var applicationActivities: [UIActivity]?

    var isPresented: Binding<Bool>

    init(activityItems: [Any], applicationActivities: [UIActivity]? = nil, isPresented: Binding<Bool>) {
        self.activityItems = activityItems
        self.applicationActivities = applicationActivities
        self.isPresented = isPresented
        super.init(nibName: nil, bundle: nil)
    }

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

    override func didMove(toParent parent: UIViewController?) {
        super.didMove(toParent: parent)
        updateState()
    }

    fileprivate func updateState() {
        guard parent != nil else {return}
        let isActivityPresented = presentedViewController != nil
        if isActivityPresented != isPresented.wrappedValue {
            if !isActivityPresented {
                let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
                controller.completionWithItemsHandler = { (activityType, completed, _, _) in
                    self.isPresented.wrappedValue = false
                }
                present(controller, animated: true, completion: nil)
            }
            else {
                self.presentedViewController?.dismiss(animated: true, completion: nil)
            }
        }
    }
}

struct ActivityViewTest: View {
    @State private var isActivityPresented = false
    var body: some View {
        Button("Preset") {
            self.isActivityPresented = true
        }.background(ActivityView(activityItems: ["Hello, World"], isPresented: $isActivityPresented))
    }
}

struct ActivityView_Previews: PreviewProvider {
    static var previews: some View {
        ActivityViewTest()
    }
}
Oestrin answered 8/4, 2020 at 14:18 Comment(2)
This crashes on iPad.Build
Just in case anyone landed here. This solution works flawlessly given it crashes on iPad. However, it has an issue that it might cause other sheets attached to the same view to dismiss automatically when presented. This change fixes the issue: "let isActivityPresented = presentedViewController as? UIActivityViewController != nil"Severen
G
6

If you need more granular control over the content displayed in the share sheet, you will probably end implementing UIActivityItemSource. I tried using Mike W.'s code above but it didn't work at first (the delegate functions weren't being called). The fix was changing the initialisation of UIActivityController within makeUIViewController as follows, now passing [context.coordinator] as activityItems:

let controller = UIActivityViewController(activityItems: [context.coordinator], applicationActivities: applicationActivities)

Also, I wanted to be able to set the icon, title and subtitle in the share sheet, so I have implemented func activityViewControllerLinkMetadata in the Coordinator class.

The following is the complete expanded version of Mike W.'s answer. Please note you will need to add import LinkPresentation to the code.

ActivityViewController

import SwiftUI
import LinkPresentation

struct ActivityViewController: UIViewControllerRepresentable {
    var shareable : ActivityShareable?
    var applicationActivities: [UIActivity]? = nil

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
        let controller = UIActivityViewController(activityItems: [context.coordinator], applicationActivities: applicationActivities)
        controller.modalPresentationStyle = .automatic
        return controller
    }

    func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {}

    func makeCoordinator() -> ActivityViewController.Coordinator {
        Coordinator(self.shareable)
    }

    class Coordinator : NSObject, UIActivityItemSource {
        private let shareable : ActivityShareable?

        init(_ shareable: ActivityShareable?) {
            self.shareable = shareable
            super.init()
        }

        func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
            guard let share = self.shareable else { return "" }
            return share.getPlaceholderItem()
        }

        func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
            guard let share = self.shareable else { return "" }
            return share.itemForActivityType(activityType: activityType)
        }

        func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
            guard let share = self.shareable else { return "" }
            return share.subjectForActivityType(activityType: activityType)
        }
        
        func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
            guard let share = self.shareable else { return nil }
            
            let metadata = LPLinkMetadata()

            // share sheet preview title
            metadata.title = share.shareSheetTitle()
            // share sheet preview subtitle
            metadata.originalURL = URL(fileURLWithPath: share.shareSheetSubTitle())
            // share sheet preview icon
            if let image = share.shareSheetIcon() {
                let imageProvider = NSItemProvider(object: image)
                metadata.imageProvider = imageProvider
                metadata.iconrovider = imageProvider
            }
            return metadata
        }
    }
}   

Protocol ActivityShareable

protocol ActivityShareable {
    func getPlaceholderItem() -> Any
    func itemForActivityType(activityType: UIActivity.ActivityType?) -> Any?
    func subjectForActivityType(activityType: UIActivity.ActivityType?) -> String
    func shareSheetTitle() -> String
    func shareSheetSubTitle() -> String
    func shareSheetIcon() -> UIImage?
}

In my case I am using the share sheet to export text, so I created a struct called ActivityShareableText that conforms to ActivityShareable:

struct ActivityShareableText: ActivityShareable {
    let text: String
    let title: String
    let subTitle: String
    let icon: UIImage?
    
    func getPlaceholderItem() -> Any {
        return text
    }
    
    func itemForActivityType(activityType: UIActivity.ActivityType?) -> Any? {
        return text
    }
    
    func subjectForActivityType(activityType: UIActivity.ActivityType?) -> String {
        return "\(title): \(subTitle)"
    }
    
    func shareSheetTitle() -> String {
        return title
    }
    
    func shareSheetSubTitle() -> String {
        return subTitle
    }
    
    func shareSheetIcon() -> UIImage? {
        return icon
    }
}

In my code, I call the share sheet as follows:

ActivityViewController(shareable: ActivityShareableText(
    text: myShareText(),
    title: myShareTitle(),
    subTitle: myShareSubTitle(),
    icon: UIImage(named: "myAppLogo")
))
Gentille answered 6/3, 2021 at 8:25 Comment(3)
this is by far the best and most robust answerMammet
and is there a way to set AttributedString for the subject ?Mammet
Amazing answer. Do you know if it's possible to extend title spacing? My title is cut.Ingredient
S
5

I got it to work now using

.sheet(isPresented: $isSheet, content: { ActivityViewController() }

.presentation is deprecated

It takes up the whole screen iOS 13 style.

Semi answered 10/8, 2019 at 19:5 Comment(3)
Do you mean UIActivityViewController() in the closure?Albie
@Albie No, ActivityViewController in this case would be a SwiftUI view wrapping a normal UIActivityViewController.Delhi
Maybe add that it's a custom wrapper.Meristic
E
5

Extending upon @Shimanski Artem solution. I think we can write that code more concise. So I basically embed my ActivityViewController in a blank UIViewController and present it from there. This way we don't get the full 'overlay' sheet and you get the native behaviour. Just like @Shimanski Artem did.

struct UIKitActivityView: UIViewControllerRepresentable {
    @Binding var isPresented: Bool

    let data: [Any]

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

    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
        }
    }
}

Usage

struct ActivityViewTest: View {
    @State private var isActivityPresented = false

    var body: some View {
        Button("Preset") {
           self.isActivityPresented = true
        }
        .background(
            UIKitActivityView(
                isPresented: $viewModel.showShareSheet,
                data: ["String"]
            )
        )
    }
}
Extinguish answered 31/7, 2021 at 19:49 Comment(1)
Your code will not work on iPads, only iPhones as no sourceView has been provided.Yttria
R
4

FWIW - Providing a slight improvement to answers that includes an implementation for UIActivityItemSource. Code simplified for brevity, specifically around the default return on itemForActivityType and activityViewControllerPlaceholderItem, they must always return the same type.

ActivityViewController

struct ActivityViewController: UIViewControllerRepresentable {

    var activityItems: [Any]
    var shareable : ActivityShareable?
    var applicationActivities: [UIActivity]? = nil

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
        let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
        controller.modalPresentationStyle = .automatic
        return controller
    }

    func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {}

    func makeCoordinator() -> ActivityViewController.Coordinator {
        Coordinator(self.shareable)
    }

    class Coordinator : NSObject, UIActivityItemSource {

        private let shareable : ActivityShareable?

        init(_ shareable: ActivityShareable?) {
            self.shareable = shareable
            super.init()
        }

        func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
            guard let share = self.shareable else { return "" }
            return share.getPlaceholderItem()
        }

        func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
            guard let share = self.shareable else { return "" }
            return share.itemForActivityType(activityType: activityType)
        }

        func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
            guard let share = self.shareable else { return "" }
            return share.subjectForActivityType(activityType: activityType)
        }
    }
}

ActivityShareable

protocol ActivityShareable {

    func getPlaceholderItem() -> Any
    func itemForActivityType(activityType: UIActivity.ActivityType?) -> Any?

    /// Optional
    func subjectForActivityType(activityType: UIActivity.ActivityType?) -> String
}

extension ActivityShareable {

    func subjectForActivityType(activityType: UIActivity.ActivityType?) -> String {
        return ""
    }
}

You could pass in the reference for ActivityViewController or the underlying UIActivityViewController but that feels unnecessary.

Restitution answered 9/1, 2020 at 4:2 Comment(0)
S
4

With iOS 16+ you can use the SwiftUI native ShareLink.

To share a basic URL or String you can use the simple syntax like:

ShareLink("Share URL", item: URL(string: "https://developer.apple.com/xcode/swiftui/")!)

If you want to share any other data like a location you need to conform the item to the Transferable protocol.
More information on that in Apple’s Documentation or in this tutorial: https://swiftwithmajid.com/2023/03/28/sharing-content-in-swiftui/

Sickness answered 28/2 at 8:52 Comment(0)
A
3

You could try porting UIActivityViewController to SwiftUI as follows:

struct ActivityView: UIViewControllerRepresentable {

    let activityItems: [Any]
    let applicationActivities: [UIActivity]?

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
        return UIActivityViewController(activityItems: activityItems,
                                        applicationActivities: applicationActivities)
    }

    func updateUIViewController(_ uiViewController: UIActivityViewController,
                                context: UIViewControllerRepresentableContext<ActivityView>) {

    }
}

but the app will crash when you try to display it.

I tried: Modal, Popover and NavigationButton.

To test it:

struct ContentView: View {
    var body: some Body {
        EmptyView
        .presentation(Modal(ActivityView()))
    }
}

It doesn't seem to be usable from SwiftUI.

Allhallows answered 11/6, 2019 at 4:50 Comment(0)
M
2

Example using SwiftUIX

There is a library called SwiftUIX that already has a wrapper for UIActivityViewController. See quick skeleton of how to present it via .sheet() which should be placed somewhere in the var body: some View {}.

import SwiftUIX

/// ...

@State private var showSocialsInviteShareSheet: Bool = false

// ...

.sheet(isPresented: $showSocialsInviteShareSheet, onDismiss: {
    print("Dismiss")
}, content: {
    AppActivityView(activityItems: [URL(string: "https://www.apple.com")!])
})
Mokpo answered 23/6, 2020 at 22:58 Comment(1)
This takes up the whole screen on iPhone, instead of the normal half screen you'd expect to see.Scrape
D
2

Suggest another way to solve it 🤔

You can create the Empty View Controller to present the sheet

struct ShareSheet: UIViewControllerRepresentable {
  // To setup the share sheet
  struct Config {
    let activityItems: [Any]
    var applicationActivities: [UIActivity]?
    var excludedActivityTypes: [UIActivity.ActivityType]?
  }

  // Result object
  struct Result {
    let error: Error?
    let activityType: UIActivity.ActivityType?
    let completed: Bool
    let returnedItems: [Any]?
  }

  @Binding var isPresented: Bool
  private let shareSheet: UIActivityViewController

  init(
    isPresented: Binding<Bool>,
    config: Config,
    onEnd: ((Result) -> Void)? = nil
  ) {
    self._isPresented = isPresented
    shareSheet = UIActivityViewController(
      activityItems: config.activityItems,
      applicationActivities: config.applicationActivities
    )
    shareSheet.excludedActivityTypes = config.excludedActivityTypes
    shareSheet.completionWithItemsHandler = { activityType, completed, returnedItems, error in
      onEnd?(
        .init(
          error: error,
          activityType: activityType,
          completed: completed,
          returnedItems: returnedItems
        )
      )
      // Set isPresented to false after complete
      isPresented.wrappedValue = false
    }
  }

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

  func updateUIViewController(
    _ uiViewController: UIViewController,
    context: Context
  ) {
    if isPresented, shareSheet.view.window == nil {
      uiViewController.present(shareSheet, animated: true, completion: nil)
    } else if !isPresented, shareSheet.view.window != nil {
      shareSheet.dismiss(animated: true)
    }
  }
}

You can also create the operator in the view extension

extension View {
  func shareSheet(
    isPresented: Binding<Bool>,
    config: ShareSheet.Config,
    onEnd: ((ShareSheet.Result) -> Void)? = nil
  ) -> some View {
    self.background(
      ShareSheet(isPresented: isPresented, config: config, onEnd: onEnd)
    )
  }
}
Donnelldonnelly answered 29/7, 2021 at 13:22 Comment(0)
M
0

Thanks for the helpful answers in this thread.

I tried to solve the stale data problem. The issue from not not implementing updateUIViewController in UIViewControllerRepresentable. SwiftUI calls makeUIViewController only once to create the view controller. The method updateUIViewController is responsible to make changes to view controller based on changes of the SwiftUI view.

As UIActivityViewController does not allow to change activityItems and applicationActivities, I used a wrapper view controller. UIViewControllerRepresentable will update the wrapper and the wrapper will create a new UIActivityViewController as needed to perform the update.

Below my code to implement a "share" button in my application. The code is tested on iOS 13.4 beta, which has fixed several SwiftUI bugs - not sure if it works on earlier releases.

struct Share: View {
    var document: ReaderDocument   // UIDocument subclass
    @State var showShareSheet = false

    var body: some View {
        Button(action: {
            self.document.save(to: self.document.fileURL, for: .forOverwriting) { success in
                self.showShareSheet = true
            }
        }) {
            Image(systemName: "square.and.arrow.up")
        }.popover(isPresented: $showShareSheet) {
            ActivityViewController(activityItems: [ self.document.text, self.document.fileURL,
                                                    UIPrintInfo.printInfo(), self.printFormatter ])
              .frame(minWidth: 320, minHeight: 500)  // necessary for iPad
        }
    }

    var printFormatter: UIPrintFormatter {
        let fontNum = Preferences.shared.readerFontSize.value
        let fontSize = ReaderController.readerFontSizes[fontNum < ReaderController.readerFontSizes.count ? fontNum : 1]
        let printFormatter = UISimpleTextPrintFormatter(text: self.document.text)
        printFormatter.startPage = 0
        printFormatter.perPageContentInsets = UIEdgeInsets(top: 72, left: 72, bottom: 72, right: 72)
        return printFormatter
    }
}

struct ActivityViewController: UIViewControllerRepresentable {

    var activityItems: [Any]
    var applicationActivities: [UIActivity]? = nil
    @Environment(\.presentationMode) var presentationMode

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>)
        -> WrappedViewController<UIActivityViewController> {
        let controller = WrappedViewController(wrappedController: activityController)
        return controller
    }

    func updateUIViewController(_ uiViewController: WrappedViewController<UIActivityViewController>,
                                context: UIViewControllerRepresentableContext<ActivityViewController>) {
        uiViewController.wrappedController = activityController
    }

    private var activityController: UIActivityViewController {
        let avc = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
        avc.completionWithItemsHandler = { (_, _, _, _) in
            self.presentationMode.wrappedValue.dismiss()
        }
        return avc
    }
}

class WrappedViewController<Controller: UIViewController>: UIViewController {
    var wrappedController: Controller {
        didSet {
            if (wrappedController != oldValue) {
                oldValue.removeFromParent()
                oldValue.view.removeFromSuperview()
                addChild(wrappedController)
                view.addSubview(wrappedController.view)
                wrappedController.view.frame = view.bounds
            }
        }
    }

    init(wrappedController: Controller) {
        self.wrappedController = wrappedController
        super.init(nibName: nil, bundle: nil)
        addChild(wrappedController)
    }

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

    override func loadView() {
        super.loadView()
        view.addSubview(wrappedController.view)
        wrappedController.view.frame = view.bounds
    }
}
Myrick answered 22/2, 2020 at 23:58 Comment(0)
A
0

Swift 5 / SwiftUI Native

Simple, with completion call-back and native SwiftUI @Binding

import SwiftUI

struct ShareSheet: UIViewControllerRepresentable {
    typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
    
    @Binding var isPresented: Bool
    @Binding var activityItem: String
    
    let applicationActivities: [UIActivity]? = nil
    let excludedActivityTypes: [UIActivity.ActivityType]? = nil
    let callback: Callback?
    
    func makeUIViewController(context: Context) -> UIActivityViewController {
        
        let controller = UIActivityViewController(
            activityItems: [activityItem],
            applicationActivities: applicationActivities)
        
        controller.excludedActivityTypes = excludedActivityTypes
        controller.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
            callback?(activityType, completed, returnedItems, error)
            isPresented = false
        }
        
        return controller
    }
    
    func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
        
    }
}

example usage:

ShareSheet(isPresented: $showShareSheet, activityItem: $sharingUrl, callback: { activityType, completed, returnedItems, error in
    
    print("ShareSheet dismissed: \(activityType) \(completed) \(returnedItems) \(error)")
                    
})
Alishiaalisia answered 20/3, 2021 at 5:56 Comment(0)
P
0

Just use introspect. Then you can easily code something like this:

YourView().introspectViewController { controller in
    guard let items = viewModel.inviteLinkParams, viewModel.isSharePresented else { return }
    let activity = UIActivityViewController(activityItems: items, applicationActivities: nil)
    controller.present(activity, animated: true, completion: nil)
}
Premiere answered 25/3, 2021 at 14:9 Comment(2)
Where did you even get this introspectViewController? I can't find it in SwiftUI standard lib.Snakemouth
Sorry, it's a Library: github.com/siteline/SwiftUI-IntrospectPremiere

© 2022 - 2024 — McMap. All rights reserved.