SwiftUI and UIKit are both powerful frameworks for building user interfaces in iOS applications. While SwiftUI offers a modern, declarative approach, UIKit has been the foundation of iOS development for many years and has a vast collection of pre-built components and legacy code. Integrating these two frameworks can be highly beneficial, especially when migrating existing UIKit-based projects to SwiftUI or when you need to use UIKit components that are not yet available in SwiftUI.
Why Integrate UIKit and SwiftUI?
- Leverage Existing UIKit Code: Re-use existing custom UIKit views and controllers in SwiftUI projects.
- Gradual Migration: Migrate parts of your app to SwiftUI while keeping other parts in UIKit.
- Access UIKit-Specific Features: Use UIKit features that are not yet available in SwiftUI.
How to Integrate UIKit with SwiftUI
SwiftUI provides two protocols to facilitate the integration of UIKit elements: UIViewRepresentable
and UIViewControllerRepresentable
.
1. Using UIViewRepresentable
UIViewRepresentable
is used to wrap a UIView
(a basic building block for UI in UIKit) so it can be used in SwiftUI.
Step 1: Create a UIKit View
Let’s start by creating a simple custom UIView
.
import UIKit
class CustomUIView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
backgroundColor = .systemBlue
let label = UILabel()
label.text = "Custom UIKit View"
label.textColor = .white
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
}
In this example, CustomUIView
is a simple view with a blue background and a centered label.
Step 2: Conform to UIViewRepresentable
To use the CustomUIView
in SwiftUI, create a struct that conforms to UIViewRepresentable
.
import SwiftUI
struct CustomUIViewRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> CustomUIView {
return CustomUIView()
}
func updateUIView(_ uiView: CustomUIView, context: Context) {
// Update the view if needed
}
}
The UIViewRepresentable
protocol requires two functions:
makeUIView(context:)
: Creates and returns an instance of your UIKit view.updateUIView(_:context:)
: Updates the state of the UIKit view. It is called when the SwiftUI state changes.
Step 3: Use in SwiftUI
You can now use CustomUIViewRepresentable
in your SwiftUI view.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("SwiftUI View")
.font(.title)
.padding()
CustomUIViewRepresentable()
.frame(width: 300, height: 200)
Spacer()
}
}
}
2. Using UIViewControllerRepresentable
UIViewControllerRepresentable
is used to wrap a UIViewController
(a controller that manages a view hierarchy in UIKit) so it can be used in SwiftUI.
Step 1: Create a UIKit ViewController
Let’s create a simple UIViewController
with a background color and a label.
import UIKit
class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
private func setupView() {
view.backgroundColor = .systemGreen
let label = UILabel()
label.text = "Custom UIKit ViewController"
label.textColor = .white
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
}
Step 2: Conform to UIViewControllerRepresentable
Create a struct that conforms to UIViewControllerRepresentable
.
import SwiftUI
struct CustomUIViewControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> CustomViewController {
return CustomViewController()
}
func updateUIViewController(_ uiViewController: CustomViewController, context: Context) {
// Update the view controller if needed
}
}
The UIViewControllerRepresentable
protocol requires two functions:
makeUIViewController(context:)
: Creates and returns an instance of your UIKit view controller.updateUIViewController(_:context:)
: Updates the state of the UIKit view controller.
Step 3: Use in SwiftUI
Now you can use CustomUIViewControllerRepresentable
in your SwiftUI view.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("SwiftUI View")
.font(.title)
.padding()
CustomUIViewControllerRepresentable()
.frame(width: 300, height: 200)
Spacer()
}
}
}
Passing Data Between SwiftUI and UIKit
Passing data between SwiftUI and UIKit views is a common requirement when integrating the two frameworks. Here’s how to handle data passing for both UIViewRepresentable
and UIViewControllerRepresentable
.
Passing Data with UIViewRepresentable
You can pass data to your UIKit view by adding properties to the UIViewRepresentable
struct. The updateUIView
method is then used to update the UIKit view with the new data.
Step 1: Add Properties to UIViewRepresentable
import SwiftUI
struct DataDrivenUIViewRepresentable: UIViewRepresentable {
var text: String
var textColor: UIColor
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
label.textAlignment = .center
return label
}
func updateUIView(_ uiView: UILabel, context: Context) {
uiView.text = text
uiView.textColor = textColor
}
}
Step 2: Use in SwiftUI with Data
import SwiftUI
struct ContentView: View {
@State private var message: String = "Hello UIKit!"
var body: some View {
VStack {
Text("SwiftUI View")
.font(.title)
.padding()
DataDrivenUIViewRepresentable(text: message, textColor: .systemPink)
.frame(width: 300, height: 100)
Button("Change Text") {
message = "Updated Text!"
}
.padding()
Spacer()
}
}
}
Passing Data with UIViewControllerRepresentable
Similar to UIViewRepresentable
, you can pass data to your UIKit view controller using properties in the UIViewControllerRepresentable
struct.
Step 1: Add Properties to UIViewControllerRepresentable
import SwiftUI
struct DataDrivenUIViewControllerRepresentable: UIViewControllerRepresentable {
var backgroundColor: UIColor
func makeUIViewController(context: Context) -> UIViewController {
let viewController = UIViewController()
return viewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
uiViewController.view.backgroundColor = backgroundColor
}
}
Step 2: Use in SwiftUI with Data
import SwiftUI
struct ContentView: View {
@State private var color: Color = .blue
var body: some View {
VStack {
Text("SwiftUI View")
.font(.title)
.padding()
DataDrivenUIViewControllerRepresentable(backgroundColor: UIColor(color))
.frame(width: 300, height: 100)
Button("Change Color") {
color = Color.red
}
.padding()
Spacer()
}
}
}
Context and Coordination
Sometimes, you need more advanced interactions between SwiftUI and UIKit. This can be achieved using the Context
parameter and a Coordinator class within UIViewRepresentable
and UIViewControllerRepresentable
.
Coordinator
The Coordinator
is a helper class that facilitates communication from UIKit to SwiftUI. It is useful when you need to send data or trigger updates from the UIKit view back to the SwiftUI environment.
Step 1: Create a Coordinator
import SwiftUI
struct CoordinatorUIViewRepresentable: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: CoordinatorUIViewRepresentable
init(_ parent: CoordinatorUIViewRepresentable) {
self.parent = parent
}
func textFieldDidChangeSelection(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
}
}
Step 2: Use in SwiftUI
import SwiftUI
struct ContentView: View {
@State private var text: String = ""
var body: some View {
VStack {
Text("SwiftUI View")
.font(.title)
.padding()
CoordinatorUIViewRepresentable(text: $text)
.frame(width: 300, height: 50)
.border(Color.gray)
Text("Text: \(text)")
.padding()
Spacer()
}
}
}
Conclusion
Integrating UIKit with SwiftUI allows developers to combine the strengths of both frameworks. By using UIViewRepresentable
and UIViewControllerRepresentable
, you can seamlessly embed UIKit components into your SwiftUI views, pass data between them, and create advanced interactions using Coordinators. This approach is particularly useful for migrating existing UIKit projects to SwiftUI and leveraging UIKit features not yet available in SwiftUI.