One of the new features of Xcode 11 is the ability to see a live preview of a SwiftUI view in what is called the Canvas. This is really convenient while working with a UI as you can modify the code and have an immediate feedback about the result.
Wouldn’t it be cool to have a similar functionality with UIKit? Maybe you’re not ready to use SwiftUI in your project, but you could really use a live preview of your view in the design phase.
I have a good news for you, this is possible, you still have to use SwiftUI but only to wrap your existing UIKit view for the purpose of testing.
There are two ways: the quickest one is to add a new Swift file to your project, import SwiftUI and instantiate your UIKit view there, or you can create a new project and import only the classes you want to test.
Edit: I shared on GitHub the test project I made based on an old example about networking code. I used the first approach, create a new file inside an existing repository. You can find the example here
UIViewRepresentable
As you probably know you can have UIKit and SwiftUI in the same project. You can embed SwiftUI views inside UIViews and viceversa. In order to see live previews we need to embed everything inside a SwiftUI View, so we need to use UIViewRepresentable. I give you the link to the official documentation and I’m sure you’ll find plenty of examples about it, so I won’t go into details here. All you need to know for the sake of our example is that you can create a SwiftUI View conforming to UIViewRepresentable and you have to provide a makeUIView function where you can return a UIKit view. You can see the file here
struct UserViewControllerPreview: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let navigationController = UINavigationController()
let usersTVC = UsersTableViewController()
let users = DataSourceCommon.getTestUsers()
userVC.setUser(users)
navigationController.setViewControllers([usersTVC], animated: false)
return navigationController.view
}
Inside makeUIView we can instantiate a UIViewController and we need to return a UIView, in this example I embedded a TableViewController inside a UINavigationController so I return the latter.
Showing the preview
Implementing a UIViewRepresentable is the first step, we then have to create another struct conforming to PreviewProvider.
struct Test_Preview: PreviewProvider {
static var previews: some View {
return UsersTableViewControllerPreview()
}
}
This is quite simple if you know SwiftUI, you have to return some View, so something that is a View. The struct we just created is in fact a View, it’s body is made of a UIKit view but it doesn’t matter.
Every time we make a change to UsersTableViewController we can resume the preview and quickly see the results, no need to build and run in the simulator! Actually Xcode builds something every time you change the swift file, but it is way faster than launching the whole app.
If Xcode closes the canvas every time you edit something that isn’t a SwitUI file (happens all the time to me) I suggest keeping the SwiftUI file opened on the side (see my screenshot below) so you can edit the UIKit stuff and have the canvas always visible to see the changes.
Need to test more views? Just make more wrappers with UIViewRepresentable and instantiate them in Test_Preview, you only need to comment out a line and return a new View to preview it.
Conclusion
In order to take advantage of live previews your views should be easy to instantiate with mock data, as you can’t start your application like you’d do in the simulator. This is a good idea for UI automated tests too, so follow this approach even if you don’t care about Xcode previews.
You need SwiftUI that is supported from iOS 13, if you target older versions of iOS you need to put all the SwiftUI stuff inside a #if canImport(SwiftUI) or use @available.