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
Downloading and Caching Images from Url Asynchronously
Color Attribute Is Ignored in Nsattributedstring with Nslinkattributename
When Did 'Guard Let Foo = Foo' Become Legal
Ambiguous Reference to Member 'Tableview'
Implicitly Unwrapped Optional Made Immutable
Get Subdirectories Using Swift
Checking for Nil Value in Swift Dictionary Extension
How to Restrict Certain Characters in Uitextfield in Swift
How to Use Generic Types to Get Object with Same Type
Querying Below Autoid's in Firebase
Masking an Image in Swift Using Calayer and Uiimage
How to Get the File Creation Date Using Url Resourcevalues Method in Swift 3
Why Does Implicitly Unwrapped Optional Not Unwrap in Dictionary of Type [String:Any]
Swiftui: Can't Get the Transition of a Detailview to a Zstack in the Mainview to Work