I'm trying to make an Unity player integration inside a native SwiftUI app. The player should appear when taping a button then user can close the player from Unity and get back to the SwiftUI app.
For the moment I can manage to display the Unity player with this code
SwiftUI View from where I want to display the player
//Some SwiftUI View
NavigationView {
SomeView()
.fullScreenCover(isPresented: $applicationState.unityPlayerActive, onDismiss: {}, content: {
//This is a UIViewControllerRepresentable that call the unity.show() function from Unity Framework
UnityPlayer()
.ignoresSafeArea()
})
}
UnityPlayer.swift
struct UnityPlayer: UIViewControllerRepresentable {
func makeUIViewController(context _: Context) -> UIViewController {
let vc = UIViewController()
let unity = UnityBridge.getInstance()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
unity.show()
}
return vc
}
func updateUIViewController(_: UIViewController, context _: Context) {
// Empty.
}
}
UnityBridge.swift
...
/// Notifies the UnityFramework to show the window, and append the Unity view
/// to the given controller
///
/// - Parameter controller: Controller that will host the Unity view
public func show() {
ufw.showUnityWindow()
}
/// Triggered by Unity via `UnityFrameworkListener` when the framework unloaded
internal func unityDidUnload(_: Notification!) {
ufw.unregisterFrameworkListener(self)
UnityBridge.instance = nil
self.view?.removeFromSuperview()
NotificationCenter.default.post(name: Notification.Name(closeUnityPlayerNotification), object: nil)
}
...
This code works but when the player is closed the app become unresponsive, the player is still in foreground capturing user's inputs.
I don't have any error message.
Thanks for your help :)
For reference I have followed the work of David Peicho that explain well the basic logic
Related
SWCollaborationView was introduced as a standard UI element to manage real-time collaboration between users in iOS16+. This is explained in this Apple article. Similarly to UICloudSharingController, it's way to view participants to a share and manage sharing.
Given that UICloudSharingController is broken in iOS16+ (see this), how can I use SWCollaborationView in SwiftUI?
My failed attempts so far:
The the WWDC22 talk introducing SWCollaborationView, the speaker embedded SWCollaborationView in UIBarButtonItem(customView:). I was not able to embed UIBarButtonItem(customView:) into my SwiftUI lifecycle app, because UIBarButtonItem does not conform to UIView and therefore cannot be introduced using UIViewRepresentable.
I also tried wrapping SWCollaborationView in UIViewRepresentable and introducing it directly. The result was identical to this post. When I wrapped it in ToolbarItem, the icon appeared but no action happened on tap. When I wrapped it in ToolbarItem and Button that would open an identical collaboration view as a popover, the icon appeared (screenshot1) and on tap opened a popover where the same icon would appear (screenshot2. Only a second tap would correctly open the desired popover inherent to SWCollaborationView (screenshot3). The code for this is below.
import SwiftUI
import CloudKit
import SharedWithYou
struct DetailView: View {
var photo: Photo // some object saved in CloudKit, in this example a photo
#State var showPopover = false
#Binding var activeCover: ActiveCover? // for dismissal (not relevant for SWCollaborationView)
var body: some View {
NavigationView {
VStack {
Text("Some content of detail view")
}
.toolbar {
if let existingShare = PersistenceController.shared.existingShare(photo: photo) { // get the existing CKShare for this object (in this case we use NSPersistentCloudKitContainer.fetchShares, but that's not really relevant)
ToolbarItem(placement: .automatic) {
Button(action: {
showPopover.toggle()
}){
CollaborationView(existingShare: existingShare) // <- icon appears, but without the number of participants
}
.popover(isPresented: $showPopover) {
CollaborationView(existingShare: existingShare) // <- icon appears AGAIN, with the number of participants. Instead, we want a popover inherent to SWCollabroationView!
}
}
}
ToolbarItem(placement: .automatic) {
Button("Dismiss") { activeCover = nil }
}
}
}
}
}
struct CollaborationView: UIViewRepresentable {
let existingShare: CKShare
func makeUIView(context: Context) -> SWCollaborationView {
let itemProvider = NSItemProvider()
itemProvider.registerCKShare(existingShare,
container: PersistenceController.shared.cloudKitContainer,
allowedSharingOptions: .standard)
let collaborationView = SWCollaborationView(itemProvider: itemProvider)
collaborationView.activeParticipantCount = existingShare.participants.count
return collaborationView
}
func updateUIView(_ uiView: SWCollaborationView, context: Context) {
}
}
In SwiftUi it's called ShareLink
I submitted a TSI about this; Apple confirmed that SWCollaborationView is not compatible with SwiftUI at the moment and we should submit feedback.
Starting with iOS16, there was a problem with the implementation of AdMob interstitial advertising. I am using the SwiftUI framework. I looked at many sources, but unfortunately I did not find a solution. Link to on a similar topic, but without a solution. On iOS 15 everything works fine, but on iOS 16 the app crashes with the following error:
SwiftUI/UIViewControllerRepresentable.swift:332: Fatal error:
UIViewControllerRepresentables must be value types: InterstitialAdView
2022-11-20 15:34:32.811071+0300 MyApp[38437:1156286]
SwiftUI/UIViewControllerRepresentable.swift:332: Fatal error:
UIViewControllerRepresentables must be value types: InterstitialAdView
As I understand, to implement the UIViewControllerRepresentable protocol, you must use a struct, but the NSObject and GADFullScreenContentDelegate protocols can only be applied in a class. Question: how can the code be modified to meet the above requirements? I am using the following code (original from here):
// InterstitialAdView
final class InterstitialAdView: NSObject, UIViewControllerRepresentable, GADFullScreenContentDelegate {
//Here's the Ad Object we just created
let interstitialAd = InterstitialAd.shared.interstitialAd
#Binding var isPresented: Bool
var adUnitId: String
init(isPresented: Binding<Bool>, adUnitId: String) {
self._isPresented = isPresented
self.adUnitId = adUnitId
super.init()
interstitialAd?.fullScreenContentDelegate = self //Set this view as the delegate for the ad
}
//Make's a SwiftUI View from a UIViewController
func makeUIViewController(context: Context) -> UIViewController {
let view = UIViewController()
//Show the ad after a slight delay to ensure the ad is loaded and ready to present
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) {
self.showAd(from: view)
}
return view
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
//Presents the ad if it can, otherwise dismisses so the user's experience is not interrupted
func showAd(from root: UIViewController) {
if let ad = interstitialAd {
ad.present(fromRootViewController: root)
} else {
self.isPresented.toggle()
}
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
//Prepares another ad for the next time view presented
InterstitialAd.shared.loadAd(withAdUnitId: adUnitId)
//Dismisses the view once ad dismissed
isPresented.toggle()
}
}
I am trying to convert my Swift App into SwiftUI. In My Swift App I am having one listener class inheriting in home class. In that home class one function is calling. (Home is Viewcontroller class)
I am trying to do same in SwiftUI but as SwiftUI View is struct can't do that.
Sharing my Swift Code
extension Home: Listener {
func event(_ event: Event) {
if event == HomeEvent.check{
if let response = event as? CheckG {
handleCheck(response)
}
}
}
public protocol Listener: class {
func event(_ event: Event)
}
In SwiftUI
struct Home: View {
var body: some View {
VStack() {
Text("Home")
}
}
My question might be wrong as I am new to iOS ..Request you to guide on my query.
Thank You for help
What I want to do:
Present VC1
When VC1 is dismissed, present VC2
Problem:
When VC1 is dismissed, VC2 does not present
Dirty Fix:
Put milisecond delay. It fixes the problem, but want to know why it happens
Explanation: I get viewDidDissapear event when VC1 dismisses so I can present VC2
If you need more details, please ask.
Code:
class ViewModel {
let coordinator = Coordinator()
struct Input {
let itemSelected: Driver<IndexPath>
}
struct Output {
let presentVC1: Driver<Void>
let presentVC2: Driver<Void>
}
func transform(input: Input) -> Output {
let navigateToVC1 = input.itemSelected
.flatMap { [coordinator] in
return coordinator.transition(to: Scene.VC1)
}
let navigateToVC2 = navigateToVC1
.delay(.milliseconds(1))
.flatMap { [coordinator] in
return coordinator.transition(to: Scene.VC2)
}
return Output(presentVC1: presentVC1, presentVC2: presentVC2)
}
Coordinator code:
func transition(to scene: TargetScene) -> Driver<Void> {
let subject = PublishSubject<Void>()
switch scene.transition {
case let .present(viewController):
_ = viewController.rx
.sentMessage(#selector(UIViewController.viewDidDisappear(_:)))
.map { _ in }
.bind(to:subject)
currentViewController.present(viewController, animated: true)
return subject
.take(1)
.asDriverOnErrorJustComplete()
}
The viewDidDisappear method is called before the view controller is fully dismissed. You should not try to present the second view controller until the callback of dismiss is called.
Wherever you are dismissing your view controller, use the below instead and don't present the next view controller until after the observable emits a next event.
extension Reactive where Base: UIViewController {
func dismiss(animated: Bool) -> Observable<Void> {
Observable.create { [base] observer in
base.dismiss(animated: animated) {
observer.onNext(())
observer.onCompleted()
}
return Disposables.create()
}
}
}
I suggest you consider using my Cause-Logic-Effect architecture which contains everything you need to properly handle view controller presentation and dismissal.
https://github.com/danielt1263/CLE-Architecture-Tools
A portion of the interface is below:
/**
Presents a scene onto the top view controller of the presentation stack. The scene will be dismissed when either the action observable completes/errors or is disposed.
- Parameters:
- animated: Pass `true` to animate the presentation; otherwise, pass `false`.
- sourceView: If the scene will be presented in a popover controller, this is the view that will serve as the focus.
- scene: A factory function for creating the Scene.
- Returns: The Scene's output action `Observable`.
*/
func presentScene<Action>(animated: Bool, overSourceView sourceView: UIView? = nil, scene: #escaping () -> Scene<Action>) -> Observable<Action>
extension NSObjectProtocol where Self : UIViewController {
/**
Create a scene from an already existing view controller.
- Parameter connect: A function describing how the view controller should be connected and returning an Observable that emits any data the scene needs to communicate to its parent.
- Returns: A Scene containing the view controller and return value of the connect function.
Example:
`let exampleScene = ExampleViewController().scene { $0.connect() }`
*/
func scene<Action>(_ connect: (Self) -> Observable<Action>) -> Scene<Action>
}
struct Scene<Action> {
let controller: UIViewController
let action: Observable<Action>
}
The connect function is your view model, when its Observable completes or its subscriber disposes, the view controller will automatically dismiss.
The presentScene function is your coordinator. It handles the actual presenting and dismissing of scenes. When you dismiss and present a new scene, it will properly handle waiting until the previous view controller is dismissed before presenting the next one.
I am a newbie in IOS Programming and also SwiftUI. Coming from Java/Kotlin Android. I want to learn SwiftUI.
I have a WKWebView. I want to change SwiftUI page according to url changes in WKWebView. I have done a lot of work as you can see below. But I am struggled in navigation.
struct ContentView: View, Listener {
func onFetched() {
NavigationLink(destination: MainView()) {/* HERE this is not working.
App never goes to MainView.swift page.*/
Text("Show Detail View")
}
}
var body: some View {
NavigationView {
VStack {
WebView(authListener: self)
}.navigationBarTitle(Text("PEAKUP Velocity"))
}
}
}
struct WebView: UIViewRepresentable {
var listener: Listener
#ObservedObject var observe = observable()
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
observe.observation = uiView.observe(\WKWebView.url, options: .new) { view, change in
if let url = view.url {
self.observe.loggedIn = true // We loaded the page
self.listener.onFetched()
uiView.isHidden = true
}
}
uiView.load("https://google.com")
}
}
protocol Listener {
func onFetched()
}
Update: I tried this in onFetched():
NavigationLink(destination: MainView()) { Text("") }''''
And I tried this piece of code:
NavigationView {
NavigationLink(destination: SecondView()){
Text("Navigation Link")
}
}
Update 2: I tried this code also in onFetched:
self.presentation(Model(MainView(), onDismiss: nil))
Update 3: I tried this:
self.sheet(isPresented: $sayHello) {
MainView()
}
** Disclaimer I haven't used WKWebviews in the context of SwiftUI, but I assume same would be true. **
I would recommend looking into WKNavigationDelegate. Unless your class is of type WKWebView you need to ensure to assign a delegate to handle any navigation events.
Perhaps you'll find the below article helpful: https://www.hackingwithswift.com/articles/112/the-ultimate-guide-to-wkwebview