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 2 of 5 of this article. Click here to view the first page.

Retrieving the Metadata

The first step to presenting rich links is to get the metadata.

In Xcode, open SpinViewController.swift. Here you’ll see a large array of raywenderlich.com tutorials, some outlets from the storyboard and several methods for you to implement.

To start using the LinkPresentation framework, you first have to import it. Place this at the top of the file, right below import UIKit:

import LinkPresentation

To grab the metadata for a given URL, you’ll use LPMetadataProvider. If the fetch is successful, you’ll get back LPLinkMetadata, which contains the URL, title, image and video links, if they exist. All the properties on LPLinkMetadata are optional because there’s no guarantee the web page has them set.

Add a new provider property, right below the last @IBOutlet definition for errorLabel:

private var provider = LPMetadataProvider()

To fetch the metadata, you’ll call startFetchingMetadata(for:completionHandler:) on the provider.

Locate spin(_:) and add the following implementation:

// Select random tutorial link
let random = Int.random(in: 0..<links.count)
let randomTutorialLink = links[random]

// Re-create the provider
provider = LPMetadataProvider()

guard let url = URL(string: randomTutorialLink) else { return }

// Start fetching metadata
provider.startFetchingMetadata(for: url) { metadata, error in
  guard 
    let metadata = metadata, 
    error == nil 
    else { return }

  // Use the metadata
  print(metadata.title ?? "No Title")
}

You're probably wondering why you're recreating provider every time the user taps to spin the wheel. Well, LPMetadataProvider is a one-shot object, so you can only use an instance once. If you try to reuse it, you'll get an exception like this:

2020-01-12 19:56:17.003615+0000 Raylette[23147:3330742] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Trying to start fetching on an LPMetadataProvider that has already started. LPMetadataProvider is a one-shot object.'

But, it's a good idea to have a class-wide reference to it in case you need to use it later on in other methods.

Build and run and press the spin button a few times to make sure the URL titles get printed to the console:

Success text in console

Presenting Your Links

It's no fun just printing the title of the web page to the console, though. The real magic of rich links is to render them beautifully in the app!

Presenting a link is quite easy. The LinkPresentation framework includes LPLinkView that does all the heavy lifting for you.

Add a new property, right below provider:

private var linkView = LPLinkView()

Each time you spin the wheel, you'll create a new LPLinkView instance with the given URL and add it to stackView. Once you fetch the metadata for that particular URL, you'll add it to linkView.

Replace the current implementation of spin(_:) with the code below:

let random = Int.random(in: 0..<links.count)
let randomTutorialLink = links[random]

provider = LPMetadataProvider()
// 1
linkView.removeFromSuperview()

guard let url = URL(string: randomTutorialLink) else { return }
// 2
linkView = LPLinkView(url: url)

provider.startFetchingMetadata(for: url) { metadata, error in
  guard 
    let metadata = metadata, 
    error == nil 
    else { return }

  // 3
  DispatchQueue.main.async { [weak self] in
    // 4
    guard let self = self else { return }

    self.linkView.metadata = metadata
  }
}
// 5
stackView.insertArrangedSubview(linkView, at: 0)

In the code above, you:

  1. Remove linkView from stackView, if it's already there. You only want to present one link at a time.
  2. Initialize linkView with just the URL so while you're fetching the metadata, the user will still see something displayed.
  3. Assign the metadata to linkView. Then you use DispatchQueue to process UI changes on the main thread, since the metadata fetching executes on a background thread. If you don't, the app will crash.
  4. Use a reference to the view controller to update the interface in the background. By using [weak self] and guard let self = self, you ensure the update can proceed without causing a retain cycle — no matter what the user does while the background process is running.
  5. Add linkView to the stack view. This code runs immediately and gives the user something to see (the URL). Then, when the background process completes, it updates the view with the rich metadata.

Build and run and spin the wheel to see the link previews in action!

The app with link previews of image, icon and video links.

Some of the previews take quite a while to load, especially ones that include video links. But there's nothing that tells the user the preview is loading, so they have little incentive to stick around. You'll fix that next.

Adding an Activity Indicator

To improve the user experience when waiting for rich links to load, you'll add an activity indicator below the link view.

To do that, you'll use UIActivityIndicatorView. Take a look at SpinViewController.swift and notice it already has a property called activityIndicator. You add this property to stackView at the end of viewDidLoad().

Start animating activityIndicator by adding this line at the beginning of spin(_:):

activityIndicator.startAnimating()

Next, replace the block of code for fetching the metadata with this:

provider.startFetchingMetadata(for: url) { [weak self] metadata, error in
  guard let self = self else { return }

  guard 
    let metadata = metadata, 
    error == nil 
    else {
      self.activityIndicator.stopAnimating()
      return
  }

  DispatchQueue.main.async { [weak self] in
    guard let self = self else { return }

    self.linkView.metadata = metadata
    self.activityIndicator.stopAnimating()
  }
}

After unwrapping a couple optional values, this code tells the main queue to update the user interface by stopping the animation and setting the metadata on the linkView.

Build and run to see how much a simple activity indicator adds to the experience!

Link previews with loading indicator added.

Handling Errors

Thinking further about the user experience, it'd be nice if you let your users know when an error occurs, so they don't keep spinning the wheel in vain.

LPError defines all the errors that can occur if fetching the metadata fails:

  • .cancelled: The client cancels the fetch.
  • .failed: The fetch fails.
  • .timedOut: The fetch takes longer than allowed.
  • .unknown: The fetch fails for an unknown reason.

If the fetch fails, you'll show the user why. To do this, you'll use errorLabel in stackView. It starts hidden but you'll unhide it and assign it some sensible text based on the error you receive.