NEW: Start my new Ultimate Portfolio App course with a free Hacking with Swift+ trial! >>

SOLVED: UIActivityItemSource - how to use

Forums > SwiftUI

Can anyone help me with a more elaborate explanation of https://www.hackingwithswift.com/articles/118/uiactivityviewcontroller-by-example?

I am struggling a bit on how to interpret this article and properly using UIActivityItemSource differentiating content when sharing to Twitter vs Facebook vs email for example.

Especially I do not understand how to properly implement and use ViewController and also let items = [self].

Any more in-depth explanation is appreciated.

   

Anyone can help me here? Still struggling...

   

So you need to customize the items based on where they are going to be shared? What exactly does not work?

UIActivityViewController isn't the nicest thing to work with but with some experimenting it usually works at least OK :D

   

Hacking with Swift is sponsored by Instabug

SPONSORED Catch bugs as soon as they happen and know exactly why a crash occurred. Instabug's SDK grabs all the logs they need to fix bugs, crashes and performance issues in minutes instead of days. Get screenshots, device details, network logs, repro steps, and tons of other critical insights needed to resolve issues and prioritize product backlogs straight from your dashboard. It only takes a minute to integrate!

Get started now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Exactly, I want to share different content when sharing to Facebook, Twitter or through email. The article shows how you can add a subject when sharing through email but I get lost on how to exactly code a ViewController class that supports that, and then how to use that ViewController class.

The article just shows the opening but I assume the class should be something like?

class ViewController: UIViewController, UIActivityItemSource {
  let items = [self]
  let ac = UIActivityViewController(activityItems: items, applicationActivities: nil)
  present(ac, animated: true)
  func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
      return "The pig is in the poke"
  }

  func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
      return "The pig is in the poke"
  }
  func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
      return "Secret message"
  }
  func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
      if activityType == .postToTwitter {
          return "Download #MyAwesomeApp via @twostraws."
      } else {
          return "Download MyAwesomeApp from TwoStraws."
      }
  }
}

Now my question is how would I invoke this functionality now (e.g. show the activity sheet). I currently have this for invoking the out of the box share sheet:

let shareActivity = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
                          if let viewController = UIApplication.shared.windows.first?.rootViewController {
                              shareActivity.popoverPresentationController?.sourceView = viewController.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
                            viewController.present(shareActivity, animated: true, completion: nil)
                          }

but how can I adapt that for the ViewController class above? Should I instantiate the ViewController defined above? And if so, how?

   

I dont know how your UI setup looks like, but you probably already have a ViewController on the screen. So you can use it both as a source and a presenter of the activity view controller.

Alternatively you can create custom class/struct which will conform to the UIActivityItemSource and implement the required methods to change what gets shared depending on the destination. This is probably the cleaner pattern and will give you more flexibility. Since you can then instantiate this model object anytime you want to share something.

   

But the issue is that the sample simple doesn' work, or I do not understand it.

From the article I assume I can define a class like this:

class ViewController: UIViewController, UIActivityItemSource {
    let items = [self]
    let ac = UIActivityViewController(activityItems: items, applicationActivities: nil)
    present(ac, animated: true)

    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        return "The pig is in the poke"
    }
    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        return "The pig is in the poke"
    }
    func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
        return "Secret message"
    }
}

But let items = [self] already throws an error: "Cannot use instance member 'items' within property initializer; property initializers run before 'self' is available" ...

   

Oh I see, the sample does not make it clear enough, but the code you have does not go directly into the ViewController class.

This:

let items = [self]
    let ac = UIActivityViewController(activityItems: items, applicationActivities: nil)
    present(ac, animated: true)

Should be part of some method, it cannot be on the class level.

   

By way of example, here is the DetailViewController from Paul's Project 3 from 100 Days of Swift, adapted to illustrate the topic at hand:

import UIKit

class DetailViewController: UIViewController {

    @IBOutlet var imageView: UIImageView!

    var selectedImage: String?
    var selectedPictureNumber = 0
    var totalPictures = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        if let imageToLoad = selectedImage {
            imageView.image = UIImage(named: imageToLoad)
        }

        navigationItem.largeTitleDisplayMode = .never
        title = "Picture \(selectedPictureNumber) of \(totalPictures)"

        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(shareTapped))
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.hidesBarsOnTap = true
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewDidAppear(animated)
        navigationController?.hidesBarsOnTap = false
    }

    @objc func shareTapped() {
        let vc = UIActivityViewController(activityItems: [self], applicationActivities: [])
        vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem
        present(vc, animated: true)
    }

}

extension DetailViewController: UIActivityItemSource {
    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        return "placeholder"
    }

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

        if let activityType = activityType, activityType == .postToTwitter {
            return "post to Twitter"
        } else {
            return "placeholder"
        }
    }

}

Maybe this will help. In particular, check out the shareTapped() function, where self is used to construct the UIActivityViewController and the UIActivityItemSource protocol methods in the class extension, which are called to construct the array of items to share.

1      

Ah, I think the issue is that his and your example are not based on SwiftUI but on Swift and UIKit? How would I 'morph' that DetailViewController into a SwiftUI view? As that is what I am trying to accomplish.

   

You can do something like this:

import SwiftUI

class ItemSource: NSObject, UIActivityItemSource {

    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        return "placeholder"
    }

    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        if let activityType = activityType, activityType == .postToTwitter {
            return "post to Twitter"
        } else {
            return "placeholder"
        }
    }
}

struct ShareSheetView: View {

    var shareItems: ItemSource = {
        ItemSource()
    }()

    var body: some View {
        Button(action: share) {
            Image(systemName: "square.and.arrow.up")
                .font(.largeTitle)
        }
    }

    func share() {
        let activityController = UIActivityViewController(activityItems: [shareItems], applicationActivities: [])
        UIApplication.shared.windows.first?.rootViewController!.present(activityController, animated: true)
    }
}

1      

Thanks a lot. That looks like what I am after. Alternatively I was looking at using UIViewControllerRepresentable to represent ActivityViewController with UIActivityItemSource in SwiftUI, but this looks way simpler. Remaining question I do have is how to add more items, e.g. text and image to what you want to share. Do you need to define multipe ItemSource() objects or add logic to one ItemSource()?

   

Alternatively I was looking at using UIViewControllerRepresentable to represent ActivityViewController with UIActivityItemSource in SwiftUI, but this looks way simpler.

I tried that too, but the ActivityVC was always presented full height, not half-height like the system does it.

Remaining question I do have is how to add more items, e.g. text and image to what you want to share. Do you need to define multipe ItemSource() objects or add logic to one ItemSource()?

You can create multiple ItemSource objects. Maybe something like this:

import SwiftUI

struct ShareSheetView: View {

    @State private var showShareSheet: Bool = false

    var shareItems: [ItemSource] = {
        [
            ItemSource(dataToShare: "Placeholder"),
            ItemSource(dataToShare: UIImage(systemName: "heart.fill")!),
            //and so on...
        ]
    }()

    var body: some View {
        Button(action: share) {
            Image(systemName: "square.and.arrow.up")
                .font(.largeTitle)
        }
    }

    func share() {
        //NOTE that since shareItems is now an array of ItemSource objects, 
        //we don't need the brackets when we pass it to the VC
        let activityController = UIActivityViewController(
            activityItems: shareItems, 
            applicationActivities: []
        )
        UIApplication.shared.windows.first?.rootViewController!.present(activityController, animated: true)
    }
}

class ItemSource: NSObject, UIActivityItemSource {

    let dataToShare: Any

    init(dataToShare: Any) {
        self.dataToShare = dataToShare
    }

    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        return dataToShare
    }

    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        if let activityType = activityType, activityType == .postToTwitter {
            return "post data to Twitter"
        } else {
            return dataToShare
        }
    }
}

1      

Great feedback. Many thanks for your help!

   

Hacking with Swift is sponsored by Instabug

SPONSORED Catch bugs as soon as they happen and know exactly why a crash occurred. Instabug's SDK grabs all the logs they need to fix bugs, crashes and performance issues in minutes instead of days. Get screenshots, device details, network logs, repro steps, and tons of other critical insights needed to resolve issues and prioritize product backlogs straight from your dashboard. It only takes a minute to integrate!

Get started now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.