Screen 1 in UIViewController
import UIKit
import SwiftUI
class UIKitScreen1ViewController: UIViewController {
var userData: UserData? // Data to be passed to next screen
private let nameTextField = UITextField()
private let emailTextField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
// UI setup (name and email text fields)
nameTextField.placeholder = "Enter Name"
nameTextField.frame = CGRect(x: 50, y: 100, width: 300, height: 40)
emailTextField.placeholder = "Enter Email"
emailTextField.frame = CGRect(x: 50, y: 150, width: 300, height: 40)
let nextButton = UIButton(type: .system)
nextButton.setTitle("Next", for: .normal)
nextButton.frame = CGRect(x: 50, y: 200, width: 100, height: 40)
nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside)
view.backgroundColor = .white
view.addSubview(nameTextField)
view.addSubview(emailTextField)
view.addSubview(nextButton)
}
@objc private func nextButtonTapped() {
// Create a UserData object
userData = UserData(name: nameTextField.text ?? "", email: emailTextField.text ?? "")
// Navigate to Screen 2 (SwiftUI)
let screen2 = UIHostingController(rootView: SwiftUIScreen2(userData: userData!))
navigationController?.pushViewController(screen2, animated: true)
}
}
struct UserData {
let name: String
let email: String
}
Screen 2 in SwiftUI
struct SwiftUIScreen2: View {
@State var userData: UserData
@State private var isNextScreenActive = false
@State private var isUIKitNavbarHidden = false
var body: some View {
NavigationStack {
VStack {
Text("Name: \(userData.name)")
Text("Email: \(userData.email)")
Button("Go to Screen 3") {
isNextScreenActive = true
}
.padding()
// Navigate to Screen 3
}
.navigationTitle("Screen 2")
.navigationBarTitleDisplayMode(.inline)
.navigationDestination(
isPresented: $isNextScreenActive) {
SwiftUIScreen3(userData: userData)
Text("")
.hidden()
}
}
}
}
Screen 3 in SwiftUI
struct SwiftUIScreen3: View {
@State var userData: UserData
@State private var isNextScreenActive = false
var body: some View {
NavigationStack {
VStack {
Text("Name: \(userData.name)")
Text("Email: \(userData.email)")
Button("Go to Screen 4") {
isNextScreenActive = true
}
.padding()
}
.navigationTitle("Screen 3")
.navigationBarTitleDisplayMode(.inline)
.navigationDestination(
isPresented: $isNextScreenActive) {
UIKitScreen4ViewControllerWrapper(userData: userData)
Text("")
.hidden()
}
}
}
}
Screen 4 in UIViewController
struct UIKitScreen4ViewControllerWrapper: UIViewControllerRepresentable {
var userData: UserData
func makeUIViewController(context: Context) -> UIKitScreen4ViewController {
return UIKitScreen4ViewController(userData: userData)
}
func updateUIViewController(_ uiViewController: UIKitScreen4ViewController, context: Context) {
// You can update the view controller here if needed (not necessary in this case)
}
}
class UIKitScreen4ViewController: UIViewController {
var userData: UserData
private let nameLabel = UILabel()
private let emailLabel = UILabel()
init(userData: UserData) {
self.userData = userData
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// UI setup (name and email labels)
nameLabel.text = "Name: \(userData.name)"
nameLabel.frame = CGRect(x: 50, y: 100, width: 300, height: 40)
emailLabel.text = "Email: \(userData.email)"
emailLabel.frame = CGRect(x: 50, y: 150, width: 300, height: 40)
view.addSubview(nameLabel)
view.addSubview(emailLabel)
}
}
I am facing double navigation bar issue where I can see UIKit's nav bar and SwiftUI's nav bar. I have looked everywhere online and I can't find solution for this issue due to navigationStack in swiftUI Double navigation bar issue
I also face issue with transition from screen 3(SwiftUI) to screen 4(UIViewController)
I can not figure out a way to make this flow work. I would really appreciate it if anyone can help me out.
I have tried hiding the Navigation bar when pushing the the swiftui hosting controller but navigation flow breaks when I reach the Screen4. Ultimately decided to post here for help
Since you're mixing UIKit and SwiftUI and trying to navigate between them, you need to remove NavigationStack
because UIKitScreen1ViewController
also has a navigationController
. That's why it displays twice.
struct SwiftUIScreen2: View {
var body: some View {
NavigationStack { //<- need to be removed
...
}
}
}
navigationDestination
only works within NavigationStack
, thus replace it with NavigationLink
like below:
struct SwiftUIScreen2: View {
...
VStack {
Text("Name: \(userData.name)")
Text("Email: \(userData.email)")
NavigationLink(destination: SwiftUIScreen3(userData: userData)) {
Text("Go to screen 3")
}
.padding()
}
...
}
Do it the same with SwiftUIScreen3
. This is the output:
Another approach is wrapping all controllers into UIViewControllerRepresentable while holding a navigationPath
. And you will have a single NavigationStack
that navigates the flow through paths, as @workingdog mentioned.