Integrating SwiftUI in UIKit: Seamless Data Communication

Afsanafarheen
5 min readMay 17, 2023

--

With the growing power and efficiency of SwiftUI, it’s a great time to consider adopting SwiftUI for new feature development. However, transitioning an existing Swift application fully to SwiftUI can be time-consuming and challenging. In such cases, a practical approach is to leverage SwiftUI within your UIKit project for specific features while ensuring seamless data sharing between the two frameworks.

In this blog, we will explore how to integrate SwiftUI files into a UIKit project and establish smooth data communication between them.

USING SWIFTUI VIEW IN VIEWCONTROLLER:

Create a SwiftUI view that you want to display in the UIKit view controller.

import SwiftUI

struct MySwiftUIView: View {
var body: some View {
Text("Hello from SwiftUI")
}
}

Now, in your UIKit view controller, create an instance of UIHostingController and set its rootView property to an instance of the SwiftUI view:

import UIKit
import SwiftUI

class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

let swiftUIView = MySwiftUIView()
let hostingController = UIHostingController(rootView: swiftUIView)

addChild(hostingController)
view.addSubview(hostingController.view)

hostingController.view.translatesAutoresizingMaskIntoConstraints = false
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
hostingController.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

hostingController.didMove(toParent: self)
}
}

UIHostingController:

It is a UIKit class that acts as a bridge between SwiftUI and UIKit. It allows you to embed SwiftUI views within your UIKit-based app by providing a hosting environment for SwiftUI content.With UIHostingController, you can take advantage of SwiftUI’s declarative syntax, layout system, and reactive behavior while integrating SwiftUI views seamlessly into your existing UIKit-based user interface.

Passing data between UIKit and SwiftUI:

DELEGATE :

Define a SwiftUI view that contains the data you want to pass. Let’s say you have a SwiftUI view called MySwiftUIView with a property myData that you want to pass to a UIKit view controller.

Create a protocol in your SwiftUI view file that defines the delegate methods to pass the data:

import SwiftUI

protocol DataPassingDelegate: AnyObject {
func passData(_ data: String)
}

struct MySwiftUIView: View {
weak var delegate: DataPassingDelegate?

var body: some View {
Button("Pass Data") {
delegate?.passData("Hello from SwiftUI")
}
}
}

Now, call it in the ViewController:

import UIKit
import SwiftUI

class MyViewController: UIViewController, DataPassingDelegate {
override func viewDidLoad() {
super.viewDidLoad()

let swiftUIView = MySwiftUIView()
swiftUIView.delegate = self

// Present or embed the SwiftUI view
// ...
}

func passData(_ data: String) {
// Handle the received data
print("Received data from SwiftUI: \(data)")
}
}

CLOSURE:

Another way to pass data is by using closures.

import SwiftUI

struct MySwiftUIView: View {
let dataClosure: (String) -> Void

var body: some View {
Button("Pass Data") {
dataClosure("Hello from SwiftUI")
}
}
}
import UIKit
import SwiftUI

class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

let swiftUIView = MySwiftUIView { data in
// Handle the received data
print("Received data from SwiftUI: \(data)")
}

// Present or embed the SwiftUI view
// ...
}
}

Using more than one closure:

import SwiftUI

struct MySwiftUIView: View {
let dataClosure: (String) -> Void
let anotherClosure: () -> Void

var body: some View {
VStack {
Button("Pass Data") {
dataClosure("Hello from SwiftUI")
}
Button("Perform Action") {
anotherClosure()
}
}
}
}
import UIKit
import SwiftUI

class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

let swiftUIView = MySwiftUIView(
dataClosure: { data in
// Handle the received data
print("Received data from SwiftUI: \(data)")
},
anotherClosure: {
// Perform another action
print("Performing another action from SwiftUI")
}
)

// Present or embed the SwiftUI view
// ...
}
}

When creating an instance of the SwiftUI view (MySwiftUIView), provide the closures that handle the received data and perform other actions. In this example, the closures simply print messages to the console, but you can customise them to perform any desired actions.

You can then present or embed the SwiftUI view within the view controller as needed.

By utilising multiple closures, you can pass different types of data and handle various actions from your SwiftUI view to the UIKit view controller.

OTHER METHODS TO PASS DATA:

Besides delegation and closures, there are a few other methods you can use to pass data between a SwiftUI view and a UIKit view controller:

  1. User Defaults: You can use UserDefaults to store and retrieve data in a key-value pair format. Both SwiftUI views and UIKit view controllers can access and modify the same UserDefaults values, allowing you to share data between them. This method is suitable for simple data sharing scenarios.
  2. Notification Center: The NotificationCenter can be used to broadcast notifications from a SwiftUI view and observe them in a UIKit view controller. The view controller can register itself as an observer for specific notifications and respond when the notification is posted from the SwiftUI view. This approach is useful when you want to send events or trigger actions in the UIKit view controller based on certain events in the SwiftUI view.
  3. Environment Objects: SwiftUI provides the @EnvironmentObject property wrapper to share data across views. You can create an observable object that holds the shared data and inject it into both the SwiftUI view and the UIKit view controller. This way, both can access and update the shared data. This approach is beneficial when you have complex data that needs to be shared between multiple views and view controllers.
  4. Singleton or Global Variables: You can create a singleton class or use global variables to store and access data that needs to be shared between SwiftUI and UIKit. Both the SwiftUI view and the UIKit view controller can access the shared data directly from the singleton or global variables. However, be cautious when using global state as it can make your code harder to maintain and test.

Hope this article was helpful 😄.

Follow me on twitter : https://twitter.com/iamafsana_
Buy me coffee

Sayonara!! 😃

--

--

Afsanafarheen

I'm Afsana Farheen, an experienced iOS developer with a passion for creating innovative and user-friendly mobile applications.