Chiah Soon
Chiah Soon

Reputation: 587

How to Navigating from SwiftUI View to UIKit UIViewController

As of now, I have an application built entirely using UIKit. However, I wish to be able to start implementing some SwiftUI Views to replace some UIViewControllers.

I've been able to do this to navigate from the UIViewController to SwiftUI View on button tap:

    @IBAction func buttonTapped(_ sender: Any) {
        let newView = UIHostingController(rootView: SwiftUIView(viewObj: self.view, sb: self.storyboard, dismiss: self.dismiss) )
        view.window?.rootViewController = newView
        view.window?.makeKeyAndVisible()
    }

My question is, how would I transition from a single SwiftUI View to a UIViewController?(Since the rest of the application is in UIKit)? I've got a button in the SwiftUI View, to navigate back to the UIViewController on tap. I've tried:

  1. Passing the view and storyboard objects to the SwiftUI View, then calling doing something similar to the code above to change the current view controller. However, when tried on the simulator nothing happens.
  2. Using .present to show the SwiftUI View modally. This works and I can allow the SwiftUI View to .dismiss itself. However, this only works modally, and I hope to make this work properly (i.e change screen)

Here is my simple SwiftUI View:

struct SwiftUIView: View {
    var viewObj:UIView? // Perhaps use this for transition back?
    var sb:UIStoryboard?
    var dismiss: (() -> Void)?

    var body: some View {

        Button(action: {
            // Do something here to Transition
        }) {
            Text("This is a SwiftUI view.")
        }
    }
}

I'm having trouble understanding how to properly integrate SwiftUI into UIKit NOT the other way around, and I'm not sure if UIViewControllerRepresentable is the answer to this. Any solution to this, alternatives or helpful knowledge is very much appreciated. Thanks again!

Upvotes: 12

Views: 13758

Answers (7)

Judit
Judit

Reputation: 41

  1. To navigate from SwiftUI to UIKit view you have to create struct and confirm UIViewControllerRepresentable protocol and its relatable stubs.

First you have to create ViewControllerRepresentable struct in your swiftUI file.

   > struct UIKitViewControllerRepresentable: UIViewControllerRepresentable {
    > 
    >     func makeUIViewController(context: Context) -> MyUIKitViewController {
    >         return MyUIKitViewController()
    >     }
    > 
    >     func updateUIViewController(_ uiViewController: MyUIKitViewController, context: Context) {
    >         // Update the UI`enter code here`Kit ViewController (if needed)
    >     } }

After that, create navigation link like this

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("This is a SwiftUI View")
                    .padding()
                
                NavigationLink(destination: UIKitViewControllerRepresentable()) {
                    Text("Navigate to UIKit View")
                        .padding()
               }
            }
            .navigationTitle("SwiftUI View")
        }
    }
}

Make sure UIKitViewControllerRepresentable is accessible from the file where ContentView is defined.

  1. To present the SwiftUI view from a UIKit UIViewController, you will use UIHostingController. This allows UIKit to host a SwiftUI view.
  func navigateToSwiftUI() {
        
                    let swiftUIView = SwiftUIView()
            
                    // Wrap the SwiftUI view in a UIHostingController
                    let hostingController = UIHostingController(rootView: swiftUIView)
            
                    // Present the SwiftUI view as a new view controller
                    self.present(hostingController, animated: true, completion: nil)
            }

Upvotes: 0

gagandeep negi
gagandeep negi

Reputation: 1

Here is the example, you can navigate SwiftUi screen to Uikit View controller screen. Pass destination as LoginVCControllerRepresentable on NavigationLink click.

import Foundation
import SwiftUI
struct LoginVCControllerRepresentable: UIViewControllerRepresentable {

typealias UIViewControllerType = UIViewController

func makeUIViewController(context: UIViewControllerRepresentableContext<LoginVCControllerRepresentable>) -> UIViewController {
   
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let viewController = storyboard.instantiateViewController(withIdentifier: "LogInViewController") as! LogInViewController
      return viewController
  }





func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<LoginVCControllerRepresentable>) {}
}

Upvotes: 0

Aysel Heydarova
Aysel Heydarova

Reputation: 101

Wrap your UIViewController to UIViewControllerRepresentable wrapper.

struct LessonDetailsViewControllerWrapper: UIViewControllerRepresentable {
    typealias UIViewControllerType = UIViewController

    func makeUIViewController(context: UIViewControllerRepresentableContext<LessonDetailsViewControllerWrapper>) -> UIViewController {
        let viewController = LessonDetailsViewController()
        return viewController
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<LessonDetailsViewControllerWrapper>) {}
}

Then from your SwiftUI view Navigate using Navigation Link:

  NavigationLink(destination: LessonDetailsViewControllerWrapper()) {
                            LessonRow(lesson: lesson)
                        }

Upvotes: 4

Mazharul Belal
Mazharul Belal

Reputation: 105

Here is the example of push navigation controller from UIKit Button Press

let controller = UIHostingController(rootView: LoginViewController())
    self.navigationController?.pushViewController(controller, animated: true)

Upvotes: 0

Qaim Raza
Qaim Raza

Reputation: 33

you can change the presentation style and transition style to present the controller full screen

let vc = UIHostingController(rootView: LoginView())
vc.modalPresentationStyle = .fullScreen
vc.modalTransitionStyle = .crossDissolve
self.present(vc, animated: true)

Upvotes: 1

mrcfal
mrcfal

Reputation: 454

Ciao,

I tried to follow your approach by using closure callbacks.

struct SwiftUIView: View {
    var dismiss: (() -> Void)?
    var present: (()->Void)?

    var body: some View {
        VStack(spacing: 20) {
            Button(action: {
                self.dismiss?()
            }) {
                Text("Dismiss me")
            }
            Button(action: {
                self.present?()
            }) {
                Text("Present some UIViewController")
            }
        }
    }
}

When you present your UIHostingController, you want to implement the 2 closure callbacks:

@IBAction func buttonTapped(_ sender: Any) {
    let hostingController = UIHostingController(rootView: SwiftUIView())
    hostingController.rootView.dismiss = {
        hostingController.dismiss(animated: true, completion: nil)
    }
    hostingController.rootView.present = {
        let destination = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "VC_TO_PRESENT")
        hostingController.present(destination, animated: true, completion: nil)
    }

    present(hostingController, animated: true, completion: nil)
}

Upvotes: 11

Asperi
Asperi

Reputation: 257493

You can do the same, only in mirror order, like following (scratchy) ...

    Button(action: {
        if let vc = self.sb?.instantiateViewController(withIdentifier: "some_identifier") {
            self.viewObj?.window?.rootViewController = vc
            // or via present as alternate
            // self.viewObj?.window?.rootViewController.present(vc, animated: true, completion: nil)
        }
    }) {

Upvotes: 1

Related Questions