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?
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.
Now your users will see a rich preview immediately. It's the little things that count!
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:
- 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.
- The
originalURL
ofcurrentMetadata
allows the view to figure out what type of information it will display.Note: You must return theoriginalURL
property of the metadata because it contains type information that wouldn't exist if you merely return a newURL()
with the same string. This is easy to get wrong and can create bugs that are hard to track down. - Finally,
activityViewControllerLinkMetadata(_:)
is where the real magic happens when you extract all the juicy details fromcurrentMetadata
.
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!
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:
- Check if
currentMetadata
exists. If it does, you create an instance ofUIActivityViewController
. - Pass it
[self]
asactivityItems
. This is the important bit as it tells the activity controller to look at howSpinViewController
conforms toUIActivityItemSource
.
Build and run and tap the share button to see how smooth it is!
This swaps out iconProvider
for imageProvider
.
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.
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:
- Make a new property,
savedURLs
, which returns the array stored at the SavedURLs key inUserDefaults
. - Create
addToSaved(metadata:)
that you can call to check if a URL already exists in theSavedURLs
array inUserDefaults
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:
- Remove all the link preview subviews from
stackView
so it's completely empty. - Grab all the links and convert them to
LPLinkMetadata
objects withretrieve(urlString:)
. - 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!