Visually Rich Links Tutorial for iOS: Image Thumbnails

Generate visually rich links from the URL of a webpage. In this tutorial, you’ll transform Open Graph metadata into image thumbnail previews for an iOS app. By Lea Marolt Sonnenschein.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 5 of this article. Click here to view the first page.

Sharing Links

The LinkPresentation framework has a cool protocol, UIActivityItemSource, which you can use to pass LPLinkMetadata directly to the share sheet.

That means that instead of reaching out to the server and waiting for the link to load the title and icon asynchronously in the share sheet, you can pre-populate it with the metadata you already fetched.

Share sheet link loading gif.

Now your users will see a rich preview immediately. It's the little things that count!

Share sheet preview for loaded and not loaded metadata.

Sharing Links: UIActivityItemSource

First, keep track of the metadata by adding a new property to SpinViewController right below linkView:

private var currentMetadata: LPLinkMetadata?

Next, assign the value to currentMetadata in fetchMetadata(for:).

Add this line inside of the first if let statement in fetchMetadata(for:):

currentMetadata = existingMetadata

And add this line right before you cache the metadata, preceding the line, MetadataCache.cache(metadata: metadata):

self.currentMetadata = metadata

Now, to make use of the new LinkPresentation functionality, you have to make SpinViewController conform to UIActivityItemSource.

Add this extension outside SpinViewController, after the closing curly brace:

extension SpinViewController: UIActivityItemSource {
  // 1. Required function returning a placeholder
  func activityViewControllerPlaceholderItem(
    _ activityViewController: UIActivityViewController
  ) -> Any {
    return "website.com"
  }

  // 2. Required function that returns the actual activity item
  func activityViewController(
    _ activityViewController: UIActivityViewController, 
    itemForActivityType activityType: UIActivity.ActivityType?
  ) -> Any? {
    return currentMetadata?.originalURL
  }

  // 3. The metadata that the share sheet automatically picks up
  func activityViewControllerLinkMetadata(
    _ activityViewController: UIActivityViewController
  ) -> LPLinkMetadata? {
    return currentMetadata
  }
}

Conforming to UIActivityItemSource requires you to implement three methods:

  1. The placeholder method should return something close to the real data you intend to show in the Subject field of your activity item. However, it does not have to contain real data and it should return as fast as possible. You'll update it when the real data finishes loading. For now, a simple text string is sufficient.
  2. The originalURL of currentMetadata allows the view to figure out what type of information it will display.
    Note: You must return the originalURL property of the metadata because it contains type information that wouldn't exist if you merely return a new URL() with the same string. This is easy to get wrong and can create bugs that are hard to track down.
  3. Finally, activityViewControllerLinkMetadata(_:) is where the real magic happens when you extract all the juicy details from currentMetadata.
Note: You must return the originalURL property of the metadata because it contains type information that wouldn't exist if you merely return a new URL() with the same string. This is easy to get wrong and can create bugs that are hard to track down.

Sharing Links: View Update

To display this in the UI, you'll add a share button below the link view once the preview loads. The starter project provides a whole stack view with two activity buttons on SpinViewController; you simply have to show it!

Showing the activity buttons below the link preview.

The view to show is actionsStackView. When the link metadata is loading, you hide the view. Once the preview is loaded, you show it.

Add this line under activityIndicator.startAnimating() inside spin(_:):

actionsStackView.isHidden = true

And unhide it later by adding this to the end of resetViews(), before the closing curly brace:

actionsStackView.isHidden = false

Next, replace share(:) with this:

@IBAction func share(_ sender: Any) {
  guard currentMetadata != nil else { return }

  let activityController = UIActivityViewController(
    activityItems: [self], 
    applicationActivities: nil)
  present(activityController, animated: true, completion: nil)
}

In the code above, you:

  1. Check if currentMetadata exists. If it does, you create an instance of UIActivityViewController.
  2. Pass it [self] as activityItems. This is the important bit as it tells the activity controller to look at how SpinViewController conforms to UIActivityItemSource.

Build and run and tap the share button to see how smooth it is!

Preloaded share sheet.

This swaps out iconProvider for imageProvider.

Note: If you want to swap the icon that appears in the sheet preview, you can use code like this in fetchMetadata(for:) right before MetadataCache.cache(metadata: metadata):
if let imageProvider = metadata.imageProvider {
  metadata.iconProvider = imageProvider
}

This swaps out iconProvider for imageProvider.

if let imageProvider = metadata.imageProvider {
  metadata.iconProvider = imageProvider
}

Saving Favorites

Lastly, you want to save the tutorials you come across, because, hey, you might never see them again!

Next to the Share button you implemented in the previous section, there's also a Save button.

Highlighting the save button.

You'll store the tutorials the user wants to save under a special key in UserDefaults called savedURLs and display all the tutorials as link previews in the Saved tab in a stack view.

Go to MetadataCache.swift and add this underneath your current methods, right before the closing curly brace:

// 1
static var savedURLs: [String] {
  UserDefaults.standard.object(forKey: "SavedURLs") as? [String] ?? []
}

// 2
static func addToSaved(metadata: LPLinkMetadata) {
  guard var links = UserDefaults.standard
    .object(forKey: "SavedURLs") as? [String] else {
    UserDefaults.standard.set([metadata.url!.absoluteString], forKey: "SavedURLs")
    return
  }

  guard !links.contains(metadata.url!.absoluteString) else { return }

  links.append(metadata.url!.absoluteString)
  UserDefaults.standard.set(links, forKey: "SavedURLs")
}

In the code above, you:

  1. Make a new property, savedURLs, which returns the array stored at the SavedURLs key in UserDefaults.
  2. Create addToSaved(metadata:) that you can call to check if a URL already exists in the SavedURLs array in UserDefaults and add it to the array if it does not.

Next, go back to SpinViewController.swift and replace save(_:) with this:

@IBAction func save(_ sender: Any) {
  guard let metadata = currentMetadata else { return }
  MetadataCache.addToSaved(metadata: metadata)
  errorLabel.text = "Successfully saved!"
  errorLabel.isHidden = false
}

In the code above, you check for metadata. If any exists, you call addToSaved(metadata:) and notify the user of the success through errorLabel.

Now that you're successfully saving your favorite URLs, it's time to display the links.

Switch over to SavedViewController.swift and replace loadList() with this:

private func loadList() {
  // 1
  stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }

  // 2
  let links = MetadataCache.savedURLs
  let metadata = links.compactMap { MetadataCache.retrieve(urlString: $0) }

  // 3
  metadata.forEach { metadata in
    let linkView = LPLinkView(metadata: metadata)
    stackView.addArrangedSubview(linkView)
  }
}

In the function above, you:

  1. Remove all the link preview subviews from stackView so it's completely empty.
  2. Grab all the links and convert them to LPLinkMetadata objects with retrieve(urlString:).
  3. Add all the subviews to stackView.

Build and run. Save a couple of the tutorials, and see them appear on the Saved tab of the app!

Saved tab with saved tutorials.