Categories
iOS Swift SwiftUI UIKit

Using SwiftUI previews for UIKit views

SwiftUI provides wrappers for UIViewController and UIView on iOS. Same wrappers are also available for AppKit views on macOS. Let’s see how to use those wrappers for rendering UIKit views in SwiftUI previews and therefore benefiting from seeing changes immediately. Note that even when a project can’t support SwiftUI views because of the minimum deployment target, then this is still something what can be used when compiling the project with debug settings. Preview related code should only be compiled in debug builds and is never meant to be compiled in release builds. Before we jump in, there are two very useful shortcuts for keeping in mind: option+command+return for toggling previews and option+command+p for refreshing previews.

UIViewControllerRepresentable for wrapping UIViewControllers

UIViewControllerRepresentable is a protocol which can be used for wrapping UIViewController and representing it in SwiftUI. We can add a struct which conforms to that protocol and then creating an instance of the view controller in the makeUIViewController method. Second step is to add another struct which implements PreviewProvider protocol and which is used by Xcode for rendering previews. In simple cases we can get away only with such implementation but in more complex view controllers we would need to set up dependencies and generate example data for the preview. If need to do this, then all that code can be added to the makeUIViewController method.

import UIKit
import SwiftUI
final class ContentViewController: UIViewController {
override func loadView() {
self.view = UIView()
self.view.backgroundColor = .systemBackground
}
override func viewDidLoad() {
super.viewDidLoad()
let stackView = UIStackView(frame: .zero)
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 16)
])
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(label)
label.textColor = .systemRed
label.text = "Red text"
}
}
// MARK: SwiftUI Preview
#if DEBUG
struct ContentViewControllerContainerView: UIViewControllerRepresentable {
typealias UIViewControllerType = ContentViewController
func makeUIViewController(context: Context) -> UIViewControllerType {
return ContentViewController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}
struct ContentViewController_Previews: PreviewProvider {
static var previews: some View {
ContentViewControllerContainerView().colorScheme(.light) // or .dark
}
}
#endif
Wrapping UIViewController with UIViewControllerRepresentable.
UIViewController shown using SwiftUI

UIViewRepresentable for wrapping UIViews

UIViewRepresentable follows the same flow. In the example below, we use Group for showing two views with fixed size and different appearances at the same time.

import SwiftUI
import UIKit
final class BackgroundView: UIView {
override init(frame: CGRect) {
super.init(frame: .zero)
backgroundColor = .systemBackground
layer.cornerRadius = 32
layer.borderColor = UIColor.systemBlue.cgColor
layer.borderWidth = 14
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: SwiftUI Preview
#if DEBUG
struct BackgroundViewContainer: UIViewRepresentable {
typealias UIViewType = BackgroundView
func makeUIView(context: Context) -> UIViewType {
return BackgroundView(frame: .zero)
}
func updateUIView(_ uiView: BackgroundView, context: Context) {}
}
struct BackgroundViewContainer_Previews: PreviewProvider {
static var previews: some View {
Group {
BackgroundViewContainer().colorScheme(.light)
BackgroundViewContainer().colorScheme(.dark)
}.previewLayout(.fixed(width: 200, height: 200))
}
}
#endif
Wrapping UIView subclass with UIViewRepresentable.
Multiple UIViews shown in SwiftUI preview at the same time.

Summary

We looked into how to wrap view controllers and views for SwiftUI previews. Previews only required a little bit of code and therefore it is something what we can use for improving our workflows when working with UIKit views.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Project

UIKitInSwiftUIPreview (Xcode 11.5)

One reply on “Using SwiftUI previews for UIKit views”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s