SwiftUI and Unity - unity3d

I’m trying to integrate a Unity view in SwiftUI, I have the below code, but when I run the app I get no output, I know SpriteKit and SceneKit are possible and my unity view runs in a standard swift app, I’m wondering if swiftUI is possible.
struct ContentView: View {
var body: some View {
UnityUIView()
}
}
struct UnityUIView : UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate.startUnity()
return UnityGetGLView()!
}
func updateUIView(_ view: UIView, context: Context) {
}
}
I've tried to create a UIViewControllerRepresentable but get the same thing, The screen flashes once and then disappears, I think it's the splash screen as I changed the colour for debugging, no dice.
struct ContentView: View {
var body: some View {
TestUnityViewController()
}
}
struct TestUnityViewController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let vc = UIViewController()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.startUnity()
let unityView = UnityGetGLView()!
vc.view.backgroundColor = .red
vc.view!.addSubview(unityView)
return vc
}
func updateUIViewController(_ viewController: UIViewController, context: Context) {
}
}
If I add a delay to the UIViewControllerRepresentable, it works....interesting
struct TestUnityViewController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let vc = UIViewController()
vc.view.backgroundColor = .red
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.startUnity()
let unityView = UnityGetGLView()!
vc.view!.addSubview(unityView)
}
return vc
}
func updateUIViewController(_ viewController: UIViewController, context: Context) {
}
}

For people still having the issue. I haven't investigated yet, but if you are using the new Unity example to integrate Unity as a framework, the delay indeed fixed the issue with SwiftUI.
You can create a SwiftUI View to which the Unity view will be added:
import SwiftUI
struct TestUnityViewController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let vc = UIViewController()
UnityBridge.showUnity()
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
let unityView = UnityBridge.getAppController().rootView!
vc.view!.addSubview(unityView)
}
return vc
}
func updateUIViewController(_ viewController: UIViewController, context: Context) {}
}
struct ContentView: View {
var body: some View {
TestUnityViewController()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here, UnityBridge is a wrapper around the Unity framework instanciation, similar to the Objective C version from the repository.
The methods:
showUnity() creates the UnityFramework instance, and then calls the showUnityWindow method
getAppController calls the appController method from he UnityFramework object
In the meantime I find a better solution, this will do. I imagine there is a better way to do that, maybe an event triggered to know when Unity's view is fully ready.
EDIT:
I created an example repository to show how to integrate Unity in a SwiftUI project: https://github.com/DavidPeicho/unity-swiftui-example

Related

how to use UIViewRepresentable Coordinator delegate

I'm using Pulley a maps drawer library which is written in UIKit in a SwiftUI project. I have a SwiftUI ListView that I'm using in the project via a UIHostingController but I want to disable scrolling when the drawers position is not open and to do that I'm pretty sure I need to use one of the delegate functions Pulley provides (drawerPositionDidChange) but I'm not sure how to use the delegate in the Coordinator or if I should even try to use the delegate, maybe I just need to use some type of state variable?
Delegate in the view controller
#objc public protocol PulleyDelegate: AnyObject {
/** This is called after size changes, so if you care about the bottomSafeArea property for custom UI layout, you can use this value.
* NOTE: It's not called *during* the transition between sizes (such as in an animation coordinator), but rather after the resize is complete.
*/
#objc optional func drawerPositionDidChange(drawer: PulleyViewController, bottomSafeArea: CGFloat)
}
This is the UIViewRepresentable where I'm trying to use the delegate.
import SwiftUI
struct DrawerPosition: UIViewControllerRepresentable {
#Binding var bottomSafeArea: CGFloat?
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> some UIViewController {
let vc = PulleyViewController()
vc.delegate = context.coordinator
return vc
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// Updates the state of the specified view controller with new information from SwiftUI.
}
class Coordinator: NSObject, PulleyDrawerViewControllerDelegate {
var parent: DrawerPosition
init (_ parent: DrawerPosition) {
self.parent = parent
}
func drawerPositionDidChange(drawer: PulleyViewController, bottomSafeArea: CGFloat){
self.parent.bottomSafeArea = bottomSafeArea
}
}
}
the ListView where I want to disable the scroll.
import SwiftUI
struct ListView: View {
#State private var bottomSafeArea: CGFloat?
var body: some View {
ScrollViewReader { proxy in
VStack {
Button("Jump to #50") {
proxy.scrollTo(50)
}
List(0..<100, id: \.self) { i in
Text("Example")
.id(i)
}.scrollDisabled(bottomSafeArea == 0 ? true : false)
}
}
}
}
class ListViewVHC: UIHostingController<ListView> {
required init?(coder: NSCoder) {
super.init (coder: coder, rootView: ListView())
}
}
struct ListView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
Here is the correct way to set up a Coordinator:
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeUIViewController(context: Context) -> PullyViewController {
context.coordinator.pullyViewController
}
func updateUIViewController(_ uiViewController: PullyViewController, context: Context) {
// Updates the state of the specified view controller with new information from SwiftUI.
context.coordinator.bottomSafeAreaChanged = { bottomSafeArea in
self.bottomSafeArea = bottomSafeArea
}
}
class Coordinator: NSObject, PulleyDrawerViewControllerDelegate {
lazy var pullyViewController: PulleyViewController = {
let vc = PulleyViewController()
vc.delegate = self
return vc
}()
var bottomSafeAreaChanged: ((CGFloat) -> Void)?
func drawerPositionDidChange(drawer: PulleyViewController, bottomSafeArea: CGFloat){
bottomSafeAreaChanged?(bottomSafeArea)
}

Swift UI Convert UIView to View: Return type of property 'body' requires that 'UIView' conform to 'View'

I have a simple View in Swift:
import SwiftUI
import UIKit
import React
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func loadView() {
loadReactNativeView()
}
func loadReactNativeView() {
let jsCodeLocation = URL(string: "http://localhost:8081/index.bundle?platform=ios")!
let rootView = RCTRootView(
bundleURL: jsCodeLocation,
moduleName: "YourApp",
initialProperties: nil,
launchOptions: nil
)
self.view = rootView
}
}
struct ContentView: View {
var body: some View {
ViewController().view
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
But the compiler complains: Return type of property 'body' requires that 'UIView' conform to 'View'
Can someone help me understand what am I doing wrong? I need to use UIViewController to present the View.
Here's my main implementation as given by Swift boilerplate:
import SwiftUI
#main
struct content_iosApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
You cannot use UIView in a SwiftUI View. In order to use your ViewController in SwiftUI you need to wrap it in a UIViewControllerRepresentable like so:
struct SomeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> ViewController {
let viewController = ViewController()
//additional setup
return viewController
}
func updateUIViewController(_ uiViewController: ViewController, context: Context) {
//update Content
}
}
And your ContentView:
struct ContentView: View {
var body: some View {
SomeView()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Just make ur UIView -> UIViewRepresentable, I don't think u need a controller for your purpose.

How to disable zoom/pinch in webView of ios14?

I am currently doing a project to convert website to ios application and successfully created it. The only problem is that the application can be zoom in and out. One more thing, whenever any form is click, the form will be zoomed. I am new to xcode (1 week of knowledge) pls guide me. Thanks!
Below is my current code.
WebView.swift
import SwiftUI
import WebKit
import UIKit
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
}
}
struct SwiftUIWebView: UIViewRepresentable {
let url: URL?
func makeUIView(context: Context) -> WKWebView {
let prefs = WKWebpagePreferences()
prefs.allowsContentJavaScript = true
let config = WKWebViewConfiguration()
config.defaultWebpagePreferences = prefs
return WKWebView(
frame: .zero,
configuration: config
)
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard let myURL = url else {
return
}
let request = URLRequest (url: myURL)
uiView.load(request)
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView{
SwiftUIWebView(url: URL(string: "THE URL"))
.navigationTitle("NAVIGATION NAME")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

send an event from UIViewControllerRepresentable to swiftui view

I try to catch an event that occurs in a UIViewController in the SwiftUI view.
the architecture is in SwiftUI with an element in Swift:
The View
struct PlayGame: View {
var body: some View {
if stateProgress.stateOfGame == 0 {
CardsDeckRepresentable()
}
}
}
The UIViewControllerRepresentable
struct CardsDeckRepresentable: UIViewControllerRepresentable {
typealias UIViewControllerType = CardsDeckViewController
func makeUIViewController(context: Context) -> CardsDeckViewController {
let deck = CardsDeckViewController()
return deck
}
func updateUIViewController(_ uiViewController: CardsDeckViewController, context: Context) {
}
}
The UIViewController
class CardsDeckViewController : UIViewController, iCarouselDelegate, iCarouselDataSource{
... some code ...
func moveCardChoosen(number:Int) {
self.view.addSubview(cardMobile)
UIView.animate(withDuration: 0.5, delay: 0.0, options:[], animations: {
self.cardMobile.center.y = self.cardPlace5.center.y
}, completion: { finished in
if finished {
self.cardMobile.isHidden = true
}
})
}
}
At the end of the animation, I want to tel the swiftUIView to do update.
I tried with some ObservableObject or using the updateUIViewController function.
I can pass data from the SwiftUI View to the UIViewController through the UIViewControllerRepresentable.
But how to revive the change from UIViewController? The updateUIViewController don't seems not be called.

Implement delegates within SwiftUI Views

I am trying to implement a functionality that requires a delegate method (like NSUserActivity). Therefore I need a UIViewController that conforms to NSUserActivityDelegate (or similar other delegates), handles and hold all the required information. My problem is that I am using SwiftUI for my interface and therefore I am not using UIViewControllers. So how can I implement this functionality and still use SwiftUI for the UI. What I tried: view1 is just a normal SwiftUI View that can present (via NavigationLink) view2 which is the view where in want to implement this functionality. So I tried instead of linking view1 and view2, linking view1 to a UIViewControllerRepresentable which then handles the implementation of this functionality and adds UIHostingController(rootView: view2) as a child view controller.
struct view1: View {
var body: some View {
NavigationLink(destination: VCRepresentable()) {
Text("Some Label")
}
}
}
struct view2: View {
var body: some View {
Text("Hello World!")
}
}
struct VCRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
return implementationVC()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) { }
}
class implementationVC: UIViewController, SomeDelegate for functionality {
// does implementation stuff in delegate methods
...
override func viewDidLoad() {
super.viewDidLoad()
attachChild(UIHostingController(rootView: view2()))
}
private func attachChild(_ viewController: UIViewController) {
addChild(viewController)
if let subview = viewController.view {
subview.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(subview)
subview.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
subview.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
subview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
subview.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
viewController.didMove(toParent: self)
}
}
I am having trouble with transferring the data between my VC and my view2. So I'm wondering if there is a better way to implement such a functionality within a SwiftUI View.
You need to create a view that conforms to UIViewControllerRepresentable and has a Coordinator that handles all of the delegate functionality.
For example, with your example view controller and delegates:
struct SomeDelegateObserver: UIViewControllerRepresentable {
let vc = SomeViewController()
var foo: (Data) -> Void
func makeUIViewController(context: Context) -> SomeViewController {
return vc
}
func updateUIViewController(_ uiViewController: SomeViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(vc: vc, foo: foo)
}
class Coordinator: NSObject, SomeDelegate {
var foo: (Data) -> Void
init(vc: SomeViewController, foo: #escaping (Data) -> Void) {
self.foo = foo
super.init()
vc.delegate = self
}
func someDelegateFunction(data: Data) {
foo(data)
}
}
}
Usage:
struct ContentView: View {
var dataModel: DataModel
var body: some View {
NavigationLink(destination: CustomView(numberFromPreviousView: 10)) {
Text("Go to VCRepresentable")
}
}
}
struct CustomView: View {
#State var instanceData1: String = ""
#State var instanceData2: Data?
var numberFromPreviousView: Int // example of data passed from the previous view to this view, the one that can react to the delegate's functions
var body: some View {
ZStack {
SomeDelegateObserver { data in
print("Some delegate function was executed.")
self.instanceData1 = "Executed!"
self.instanceData2 = data
}
VStack {
Text("This is the UI")
Text("That, in UIKit, you would have in the UIViewController")
Text("That conforms to whatever delegate")
Text("SomeDelegateObserver is observing.")
Spacer()
Text(instanceData1)
}
}
}
}
Note: I renamed VCRepresentable to SomeDelegateObserver to be more indicative of what it does: Its sole purpose is to wait for delegate functions to execute and then run the closures (i.e foo in this example) you provide it. You can use this pattern to create as many functions as you need to "observe" whatever delegate functions you care about, and then execute code that can update the UI, your data model, etc. In my example, when SomeDelegate fires someDelegateFunction(data:), the view will display "Excuted" and update the data instance variable.