How to preview UIViewController in Xcode Previews

⋅ 5 min read ⋅ Xcode Xcode Previews

Table of Contents

What is Xcode Preview

Apple introduces Xcode Previews in Xcode 11 with the coming of SwiftUI.

With Xcode Previews, you can see how SwiftUI views look in real-time, and you can interact with it without running your app.

This is far superior to Storyboard and Interface Builder we have in UIKit.

Here is an example of Xcode Previews.

Xcode Previews.
Xcode Previews.

As you can see, the update reflects on the preview once we add a new code.

And you can tap a button to change the view's state.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

How to use Xcode Previews with UIViewController

You might not be aware that you can enjoy the benefit of Xcode Previews even though you are still using UIKit.

In this article, I will show you how to preview UIViewController using Xcode Previews.

To make your view controllers work with Xcode previews, you need to do three things.

  1. Import SwiftUI module.
  2. Create a struct that conforms to the PreviewProvider protocol.
  3. Wrap a view controller into a SwiftUI view using UIViewControllerRepesentable.
// 1
import SwiftUI
// 2
struct ViewControllerPreview: PreviewProvider {
// 3
static var previews: some View {
// return any UIViewControllerRepresentable here
}
}

1 Xcode preview works hand in hand with PreviewProvider. It discovers preview providers and generates previews for us. PreviewProvider is a part of the SwiftUI module, so we need to import SwiftUI to use it.
2 We create a struct that conforms to the PreviewProvider.
3 Normally, we will return a SwiftUI view from the previews static property. But we want to preview a view controller here, so we need to wrap it using the UIViewControllerRepresentable protocol.

So, we got everything ready. Let's jump to the implementation detail.

Using view controller from a Storyboard

As an example, I will try to use Xcode Preview with my existing view controller, which builds using a Storyboard.

A view controller created in a Storyboard.
A view controller created in a Storyboard.

UIViewControllerRepresentable

First, we need to convert a view controller into a SwiftUI view using UIViewControllerRepresentable.

The process is straightforward. You just need to initialize your view controller and return it from makeUIViewController(). You can read more about this in detail in how to convert a view controller to SwiftUI.

struct SwiftUIViewController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> ViewController {
// 1
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard
let viewController = storyboard.instantiateInitialViewController() as? ViewController
else {
fatalError("Cannot load ViewController from Main storyboard.")
}
// 2
return viewController
}

func updateUIViewController(_ uiViewController: ViewController, context: Context) {}
}

1 We grab the storyboard that our view controller is sitting in and instantiate the view controller from that storyboard.
2 Then, we return that view controller from func makeUIViewController(context: Context) -> ViewController.

Preview Provider

After converting a view controller to SwiftUI view, we are halfway done.

The only thing left is to use it for preview.

import SwiftUI

struct ViewControllerPreview: PreviewProvider {
static var previews: some View {
SwiftUIViewController()
}
}

Here is the result.

Preview a view controller in Xcode Preview.
Preview a view controller in Xcode Preview.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Helpers

Having to create UIViewControllerRepresentable for every view controller you want to preview is cumbersome.

If you have a lot of view controller classes, you should develop a helper method to easily wrap your view controller into UIViewControllerRepresentable.

There are many ways to tackle this problem. I can give you one example here.

In this example, I will create a PreviewContainer as a view controller wrapper.

import SwiftUI
import UIKit

// 1
struct PreviewContainer<T: UIViewController>: UIViewControllerRepresentable {
// 2
let viewController: T

// 3
init(_ viewControllerBuilder: @escaping () -> T) {
viewController = viewControllerBuilder()
}

// MARK: - UIViewControllerRepresentable
func makeUIViewController(context: Context) -> T {
// 4
return viewController
}

func updateUIViewController(_ uiViewController: T, context: Context) {}
}

1 I create a generic struct that can accept any type that is a subclass of UIViewController. We make this struct conform to UIViewControllerRepresentable since it will act as our SwiftUI view wrapper for our view controller.
2 We define an instance property that will hold a view controller.
3 We create an initializer that accepts a closure that returns a view controller. This will act as a view controller builder.
4 We return the resulting view controller from makeUIViewController().

This is how we use PreviewContainer.

import UIKit
import SwiftUI

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}

struct ViewController_Previews: PreviewProvider {
static var previews: some View {
PreviewContainer {
// 1
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let viewController = storyboard.instantiateInitialViewController() as? ViewController
else {
fatalError("Cannot load ViewController from Main storyboard.")
}

return viewController
}
}
}

1 We provide a closure that returns a view controller as an argument for PreviewContainer.

We can use this PreviewContainer to preview any view controller without a need to recreate UIViewControllerRepresentable.

PreviewContainer.
PreviewContainer.

Read more article about Xcode, Xcode Previews, or see all available topic

Enjoy the read?

If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Buy me a coffee Tweet Share
Previous
What's new in SF Symbols 5

In iOS 17, we can animate SF Symbols with the symbolEffect modifier.

Next
Little big improvements in Xcode 15

Small improvements that make a big difference in day-to-day coding.

← Home