DEV Community

Alex
Alex

Posted on

How to embed a UIKit ViewController inside a SwiftUI View

Hello community, the other day I was trying to use Xcode playground to test some UI but the experience was not the best. Xcode was crashing constantly so I decided to try another approach.

Use the SwiftUI preview to test my UIKit Views !.

So in this tutorial we're going to learn the first steps into integrating UIKit inside SwiftUI.

Let's start!

Creating the project

First let's start Xcode and create a new project, then select iOS > App and click Next. This time select SwiftUI as the interface and give it any name you want. Finally select a place to save the files and create the project.

Creating the UIKit View

Let's create a new swift file with the name of CounterViewController.swift. We are going to create our UIKit interface inside this file.

For simplicity, let's make a simple view that shows a label an a button.

import UIKit

final class CounterViewController: UIViewController {

    private var counter = 0

    private lazy var button: UIButton = {
        let button = UIButton()
        button.setTitle("Tap me from UIKit", for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private lazy var label: UILabel = {
        let label = UILabel()
        label.text = getMessage()
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        addComponents()
        addHandlers()
        addConstrains()
    }

}

private extension CounterViewController {
    func getMessage() -> String{
        "UIKit Button was tapped \(counter)"
    }

    func addConstrains() {
        NSLayoutConstraint.activate([
            label.topAnchor.constraint(equalTo: view.topAnchor, constant: 64),
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.widthAnchor.constraint(equalToConstant: 250),
            label.heightAnchor.constraint(equalToConstant: 50),
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 64),
            button.heightAnchor.constraint(equalToConstant: 48),
            button.widthAnchor.constraint(equalToConstant: 200),
        ])
    }

    func addComponents() {
        [button, label]
            .forEach { view.addSubview($0) }
    }

    func addHandlers() {
        button.addTarget(self, action: #selector(didButtonTapped), for: .touchUpInside)
    }

    @objc func didButtonTapped(_ sender: UIButton) {
        counter += 1
        label.text = getMessage()
    }

}
Enter fullscreen mode Exit fullscreen mode

the above code shows a Label that will be updated once the button is pressed.

Creating the SwiftUI binding

To embed our UIKit code inside the SwiftUI we need to create a new file that contains a struct that conforms to UIViewControllerRepresentable.

Create a new swift file called CounterView and add the following code:

import SwiftUI

struct CounterView: UIViewControllerRepresentable {

    typealias UIViewControllerType = CounterViewController

    func makeUIViewController(context: Context) -> CounterViewController {
        CounterViewController()
    }

    func updateUIViewController(_ uiViewController: CounterViewController, context: Context) {
       // Update the ViewController here
    }
}
Enter fullscreen mode Exit fullscreen mode

This struct is the wrapper we are going to use to create our SwiftUI view.

To conform to UIViewControllerRepresentable our structs needs three things

  1. typealias UIViewControllerType: This is a type alias for the UIKitClass, in our case is the ViewController.
  2. func makeUIViewController(context: Context): This functions is the one that will create an instance of our ViewController for SwiftUI
  3. func updateUIViewController(_ uiViewController: CounterViewController, context: Context): This function is called each time a change in our view state occurs, we can update our UIKit in this function.

Creating our View object in SwiftIU

Finally, let's create our binding view that we will use in SwiftUI.

To do so just instantiate the new View object and that's all.

import SwiftUI

struct ContentView: View {

    var body: some View {
        VStack {
            Text("SwiftUI + UIKit")
                .padding(48)
                .font(.title.bold())
            CounterView(labelText: $label)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Enter fullscreen mode Exit fullscreen mode

Xcode live previewing the UIKit component, you can click it on the button and it will work!.

swiftui-preview

Now you can make changes to your UIKit code and see a live update on the SwiftUI preview.

Conclusion

In this first tutorial we were able to embed a UIKit view controller inside a SwiftUI View and we were able to preview our work inside the live preview. This first attempt show us how easy is to create interoperability between SwiftUI and UIKit.

In coming tutorial, we will be adding bidirectional communication with @Binding and @State.

Thank you for reading.

Top comments (0)