Include Swiftui Views in Existing Uikit Application

Include SwiftUI views in existing UIKit application

edit 05/06/19: Added information about UIHostingController as suggested by @Departamento B in his answer. Credits go to him!



Using SwiftUI within UIKit

One can use SwiftUI components in existing UIKit environments by wrapping a SwiftUI View into a UIHostingController like this:

let swiftUIView = SomeSwiftUIView() // swiftUIView is View
let viewCtrl = UIHostingController(rootView: swiftUIView)

It's also possible to override UIHostingController and customize it to one's needs, e. g. by setting the preferredStatusBarStyle manually if it doesn't work via SwiftUI as expected.

UIHostingController is documented here.



Using UIKit within SwiftUI

If an existing UIKit view should be used in a SwiftUI environment, the UIViewRepresentable protocol is there to help! It is documented here and can be seen in action in this official Apple tutorial.


Compatibility

Please note that you cannot use SwiftUI on iOS versions < iOS 13, as SwiftUI is only available on iOS 13 and above. See this post for more information.

If you want to use SwiftUI in a project with a target below iOS 13, you need to tag your SwiftUI structs with @available(iOS 13.0.0, *) attribute.

Call UIKIT function from SWIFTUI

Here is a sample

class UserDashboardModel: ObservableObject {
@Published var totalBalance = 0.0
internal var uIViewController: UserDashboardHostVC? = nil

func getAcctBalance(){
if uIViewController != nil{
uIViewController!.getAcctBalance()
}else{
print("view controller is not connected")
}
}
}
struct UserDashboard: View {

@ObservedObject var userModel: UserDashboardModel
var body: some View{
VStack{
Button(action: {
userModel.getAcctBalance() //Call shared model
}){
Text("Load Balance")
}

Text(userModel.totalBalance.description) //Now it will change
}.frame(width: 400, height: 400, alignment: .center)
}
}
class UserDashboardHostVC: UIViewController {

var userModel = UserDashboardModel()

override func viewDidLoad() {
super.viewDidLoad()
//Crucial connection
userModel.uIViewController = self
let controller = UIHostingController(rootView: UserDashboard(userModel: self.userModel))

self.addChild(controller)
self.view.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
//Update constraints per use case
controller.view.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
controller.view.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
controller.didMove(toParent: self)

}
func getAcctBalance() {
userModel.totalBalance = 599
}
}

Replacing UIKit with SwiftUI in an existing project

Don't worry about "removing traces." Fun fact: Importing SwiftUI at the top of a file ALSO imports UIKit.

What you said about the SceneDelegate and a UIHostingController is all you need actually. The only view controller you should ever need to load is the hosting controller. You literally just have a root view (ContentView or whatever) and start your whole app from that initial view. Be creative about your structure. Now is the time to experiment.

Inserting UIKit content into a SwiftUI view hierarchy

The basic strategy is to use a View that represents the content you want to bring in from a UIViewController. Your View is going to conform to UIViewCotrollerRepresentable and use the functions of that protocol to create and manage the UIKit content.

The UIViewControllerRepresentable documentation is here

And, as was commented on your original post by vadian, there is A tutorial with sample code

With the sample code above, I would rename "ViewController" to be something like PassBaseViewController or PBViewController, then you would create a View that derives from UIViewControllerRepresentable

You end up with a file called PBViewController.swift that has your code from above:

import Passbase
import UIKit

class PBViewController: UIViewController, PassbaseDelegate {
override func viewDidLoad() {
super.viewDidLoad()
PassbaseSDK.delegate = self
// Optional - You can prefill the email to skip that step.
Passbase.prefillUserEmail = "testuser@yourproject.com"
let button = PassbaseButton(frame: CGRect(x: 40, y: 90, width: 300, height: 60))
self.view.addSubview(button)
}

... and the rest of the code from your question here ...

Then (probably in another file, but not necessarily) you could create the SwiftUIView that uses that view controller:

struct PassBaseView : UIViewControllerRepresentable {
typealias UIViewControllerType = PBViewController

func makeUIViewController(context: Context) -> PBViewController {
return PBViewController()
}

func updateUIViewController(_ uiViewController: PBViewController, context: Context) {
/* code here to make changes to the view controller if necessary when this view is updated*/
}
}


Related Topics



Leave a reply



Submit