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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Visually Rich Links Tutorial for iOS: Image Thumbnails
35 mins
- Getting Started
- Understanding Rich Links
- Understanding Rich Links: Web Page Metadata
- Understanding Rich Links: Open Graph Protocol
- Building the Preview
- Building the Preview: The URL
- Building the Preview: The Title
- Building the Preview: The Icon
- Building the Preview: The Image
- Building the Preview: The Video
- Retrieving the Metadata
- Presenting Your Links
- Adding an Activity Indicator
- Handling Errors
- Handling Errors: Error Messages
- Handling Errors: Cancel Fetch
- Storing the Metadata
- Storing the Metadata: Cache and Retrieve
- Storing the Metadata: Refactor
- Sharing Links
- Sharing Links: UIActivityItemSource
- Sharing Links: View Update
- Saving Favorites
- Using UIStackView Versus UITableView
- Where to Go From Here?
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:
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:
- Remove
linkView
fromstackView
, if it's already there. You only want to present one link at a time. - Initialize
linkView
with just the URL so while you're fetching the metadata, the user will still see something displayed. - Assign the metadata to
linkView
. Then you useDispatchQueue
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. - Use a reference to the view controller to update the interface in the background. By using
[weak self]
andguard 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. - 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!
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!
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.