Troubleshoot `hasBeenConnected` SwiftUI

TODO: collapasable, copyable, download, etc

I want to trigger a view to call ``openImmersiveSpace when connectionStatusViewModel.hasBeenConnected changes... I have ```import SwiftUI import RealityKit import RealityKitContent

struct ContentView: View {

@EnvironmentObject var connectionStatusViewModel: ControllerConnectionStatusViewModel

@Environment(\.openImmersiveSpace) var openImmersiveSpace
// TODO: call when we are dismissed ? exit app?
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace



var body: some View {
    VStack {
        
        ControllerConnectionStatusView(connectionStatusViewModel: connectionStatusViewModel)
        GeometryReader { proxy in
            let textWidth = min(max(proxy.size.width * 0.4, 300), 500)
            ZStack {
                HStack(spacing: 60) {
                    VStack(alignment: .leading, spacing: 0) {
                        Text("Laser Sword Trainer \(connectionStatusViewModel.hasBeenConnected)")
                            .font(.system(size: 50, weight: .bold))
                            .padding(.bottom, 15)
                            .onChange(of: connectionStatusViewModel.hasBeenConnected) {
                                oldValue, newValue in
                                print("onChange happened \(oldValue) \(newValue)")
                                if oldValue == false && newValue {
                                    Task {
                                        await openImmersiveSpace(id: "TrainerSpace")
                                    }
                                }```

I don't see hasBeenConnected ever fire... what am I doing wrong? Spatial computing goes even further with visionOS updates that bring enhanced support for volumetric apps, powerful new enterprise APIs, the new TabletopKit framework, and more.

visionOS Highlights

TabletopKit Create collaborative experiences centered around a table. This new framework handles the manipulation of cards and pieces, establishing placement and layout, and defining game boards.

New enterprise APIs provide access to spatial barcode scanning, the Apple Neural Engine, object tracking parameters, and more.

visionOS for enterprise

Volumetric APIs • Create apps that take full advantage of depth and space — and can

run side by side with other apps in the Shared Space.
• Resize volumes by using the SwiftUI scene

modifier windowResizability.

Decide if you want the user’s hands to appear in front of or behind your digital content.

Inputs

Getting started? Explore the visionOS Pathway on developer.apple.com 􀯻

• Detect planes in all orientations. • Allow anchoring objects on surfaces in your surroundings.
• Use Room Anchors to consider the user’s surroundings on a

per-room basis.
• Attach content to individual objects around the user with the new

Object Tracking API for visionOS.

Updates to scene understanding

import pandas as pd

Load the documentation.csv and code.csv files

documentation_df = pd.read_csv('/mnt/data/documentation.csv') code_df = pd.read_csv('/mnt/data/code.csv')

Display the structure of the dataframes to understand their content

documentation_df.head(), code_df.head()( framework category title
0 arkit Framework ARKit
1 arkit Class ARKitSession
2 arkit Protocol DataProvider
3 arkit Protocol Anchor
4 arkit API Collection ARKit in visionOS

                                         abstract  \

0 Integrate hardware sensing features to produce...
1 The main entry point for receiving data from A...
2 A source of live data from ARKit.
3 The identity, location, and orientation of an ...
4 Create immersive augmented reality experiences.

                                          content  \

0 Framework ARKit Integrate hardware sensing fea...
1 ARKit ARKitSession Class ARKit Session The mai...
2 ARKit DataProvider Protocol Data Provider A so...
3 ARKit Anchor Protocol Anchor The identity, loc...
4 Collection ARKit ARKit in visionOS API Collect...

                                     code_samples  \

0 NaN
1 let\n planeData \n=\n \nPlaneDetectionProvider...
2 NaN
3 NaN
4 NaN

                                              url  

0 https://developer.apple.com/documentation/arkit
1 https://developer.apple.com/documentation/arki...
2 https://developer.apple.com/documentation/arki...
3 https://developer.apple.com/documentation/arki...
4 https://developer.apple.com/documentation/arki... , title
0 Work with windows in SwiftUI
1 Work with windows in SwiftUI
2 Work with windows in SwiftUI
3 Work with windows in SwiftUI
4 Work with windows in SwiftUI

                                      description  \

0 Learn how to create great single and multi-win...
1 Learn how to create great single and multi-win...
2 Learn how to create great single and multi-win...
3 Learn how to create great single and multi-win...
4 Learn how to create great single and multi-win...

                    code_title  \

0 BOT-anist scenes
1 Creating the movie WindowGroup
2 Opening a movie window
3 Pushing a movie window
4 Toolbar

                                      code_sample  \

0 @main\n\n\nstruct\n \nBOTanistApp\n: \nApp\n {...
1 @main\n\n\nstruct\n \nBOTanistApp\n: \nApp\n {...
2 struct\n \nEditorContentView\n: \nView\n {\n ...
3 struct\n \nEditorContentView\n: \nView\n {\n ...
4 CanvasView\n()\n .toolbar {\n \nTool...

                                              url  

0 https://developer.apple.com/videos/play/wwdc20...
1 https://developer.apple.com/videos/play/wwdc20...
2 https://developer.apple.com/videos/play/wwdc20...
3 https://developer.apple.com/videos/play/wwdc20...
4 https://developer.apple.com/videos/play/wwdc20... )It looks like your current approach should work to trigger openImmersiveSpace when connectionStatusViewModel.hasBeenConnected changes. However, if the hasBeenConnected property is not changing as expected, here are a few steps to troubleshoot and ensure proper functionality:

  1. Ensure hasBeenConnected is being updated properly: Verify that the ControllerConnectionStatusViewModel class is correctly updating the hasBeenConnected property. There might be an issue in the logic where this property is set.

  2. Check for SwiftUI updates: Sometimes, SwiftUI views might not update as expected if the properties are not marked correctly. Ensure that hasBeenConnected is a @Published property in your ControllerConnectionStatusViewModel.

  3. Test with a simplified version: Simplify your view to isolate the issue. Ensure that the onChange modifier is correctly triggering with a simpler condition.

Here is a revised version of your code with additional checks and simplifications:

import SwiftUI
import RealityKit
import RealityKitContent

class ControllerConnectionStatusViewModel: ObservableObject {
    @Published var hasBeenConnected: Bool = false
}

struct ContentView: View {
    @EnvironmentObject var connectionStatusViewModel: ControllerConnectionStatusViewModel
    @Environment(\.openImmersiveSpace) var openImmersiveSpace
    @Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace

    var body: some View {
        VStack {
            ControllerConnectionStatusView(connectionStatusViewModel: connectionStatusViewModel)
            GeometryReader { proxy in
                let textWidth = min(max(proxy.size.width * 0.4, 300), 500)
                ZStack {
                    HStack(spacing: 60) {
                        VStack(alignment: .leading, spacing: 0) {
                            Text("Laser Sword Trainer \(connectionStatusViewModel.hasBeenConnected)")
                                .font(.system(size: 50, weight: .bold))
                                .padding(.bottom, 15)
                                .onChange(of: connectionStatusViewModel.hasBeenConnected) { oldValue, newValue in
                                    print("onChange happened \(oldValue) \(newValue)")
                                    if oldValue == false && newValue {
                                        Task {
                                            await openImmersiveSpace(id: "TrainerSpace")
                                        }
                                    }
                                }
                        }
                    }
                }
            }
        }
    }
}

Additionally, you can place some debug prints or use breakpoints to ensure the property is being updated correctly in the ViewModel.

If you continue to face issues, please check for any warnings or errors in the console, as they might provide more context on what's going wrong. If the issue is still not resolved, it might be helpful to see the implementation of the ControllerConnectionStatusViewModel class to ensure that it conforms to the expected behavior.// // ControllerConnectionStatusViewModel.swift // LaserSwordTrainer // // Created by Austin King on 6/13/24. //

import Foundation import Combine

@MainActor final class ControllerConnectionStatusViewModel: ObservableObject { private var multipeerManager: MultipeerManager private var cancellables = Set()

@Published var hasBeenConnected: Bool = false {
    didSet {
        print("hasBeenConnected changed to \(hasBeenConnected)")
    }
}
@Published var isConnected: Bool = false {
    didSet {
        print("isConnected changed to \(isConnected)")
    }
}

init(multipeerManager: MultipeerManager) {
    self.multipeerManager = multipeerManager
    self.multipeerManager.$hasBeenConnected
        .assign(to: \.hasBeenConnected, on: self)
        .store(in: &cancellables)
    self.multipeerManager.$isConnected
        .assign(to: \.isConnected, on: self)
        .store(in: &cancellables)
}

}

I do see hasBeenConnected changed to true in the logsYour ControllerConnectionStatusViewModel seems correctly implemented, and the logs indicate that hasBeenConnected is indeed being updated. Given that you see the log message, the issue might be related to the timing or the way the SwiftUI view observes this change.

Here are a few things you can try:

  1. Ensure ViewModel is correctly provided to the environment: Make sure that the ControllerConnectionStatusViewModel is correctly initialized and provided as an environment object in your app's entry point.

  2. Check if openImmersiveSpace is correctly called: Ensure the openImmersiveSpace function is available and correctly implemented.

  3. Simplify the view structure: Try simplifying your view to isolate the issue. Sometimes nested views or complex hierarchies can interfere with updates.

  4. Explicitly test the onChange handler: Place a breakpoint or print statement inside the onChange handler to verify it is triggered as expected.

Here is a slightly modified and simplified version of your code to help debug:

Main ContentView

import SwiftUI
import RealityKit
import RealityKitContent

struct ContentView: View {
    @EnvironmentObject var connectionStatusViewModel: ControllerConnectionStatusViewModel
    @Environment(\.openImmersiveSpace) var openImmersiveSpace
    @Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace

    var body: some View {
        VStack {
            ControllerConnectionStatusView(connectionStatusViewModel: connectionStatusViewModel)
            GeometryReader { proxy in
                let textWidth = min(max(proxy.size.width * 0.4, 300), 500)
                ZStack {
                    HStack(spacing: 60) {
                        VStack(alignment: .leading, spacing: 0) {
                            Text("Laser Sword Trainer \(connectionStatusViewModel.hasBeenConnected)")
                                .font(.system(size: 50, weight: .bold))
                                .padding(.bottom, 15)
                                .onChange(of: connectionStatusViewModel.hasBeenConnected) { oldValue, newValue in
                                    print("onChange triggered with oldValue: \(oldValue), newValue: \(newValue)")
                                    if oldValue == false && newValue {
                                        Task {
                                            await openImmersiveSpace(id: "TrainerSpace")
                                        }
                                    }
                                }
                        }
                    }
                }
            }
        }
    }
}

App Entry Point

Ensure your App struct correctly provides the ControllerConnectionStatusViewModel:

import SwiftUI

@main
struct LaserSwordTrainerApp: App {
    var multipeerManager = MultipeerManager()
    var connectionStatusViewModel: ControllerConnectionStatusViewModel {
        ControllerConnectionStatusViewModel(multipeerManager: multipeerManager)
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(connectionStatusViewModel)
        }
    }
}

This setup should ensure that the ControllerConnectionStatusViewModel is properly provided to the ContentView and that the onChange modifier correctly responds to changes in the hasBeenConnected property. If you still encounter issues, it might be helpful to review the MultipeerManager class to ensure it behaves as expected.The debug line onChange triggered with oldValue... never fires... Should I call .onChange somewhere else in the view instead of on the Text?If the onChange handler isn't firing, it suggests that the SwiftUI view hierarchy isn't properly observing the change. You can try moving the onChange handler to a higher level in your view hierarchy to ensure it captures the change in hasBeenConnected.

Here's an updated approach where the onChange handler is moved to a higher level in the view hierarchy:

import SwiftUI
import RealityKit
import RealityKitContent

struct ContentView: View {
    @EnvironmentObject var connectionStatusViewModel: ControllerConnectionStatusViewModel
    @Environment(\.openImmersiveSpace) var openImmersiveSpace
    @Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace

    var body: some View {
        VStack {
            ControllerConnectionStatusView(connectionStatusViewModel: connectionStatusViewModel)
            GeometryReader { proxy in
                let textWidth = min(max(proxy.size.width * 0.4, 300), 500)
                ZStack {
                    HStack(spacing: 60) {
                        VStack(alignment: .leading, spacing: 0) {
                            Text("Laser Sword Trainer \(connectionStatusViewModel.hasBeenConnected)")
                                .font(.system(size: 50, weight: .bold))
                                .padding(.bottom, 15)
                        }
                    }
                }
            }
        }
        .onChange(of: connectionStatusViewModel.hasBeenConnected) { oldValue, newValue in
            print("onChange triggered with oldValue: \(oldValue), newValue: \(newValue)")
            if oldValue == false && newValue {
                Task {
                    await openImmersiveSpace(id: "TrainerSpace")
                }
            }
        }
    }
}

By moving the onChange handler to the VStack, it ensures that any changes to hasBeenConnected are observed at a higher level, potentially resolving the issue.

Additional Debugging Tips

  1. Check for ViewModel Updates: Ensure that the ControllerConnectionStatusViewModel is properly updated and provided to the ContentView.

  2. Simplify Further: Temporarily remove other parts of the view to isolate the problem.

  3. Verify Environment Object: Ensure that the environment object is properly injected:

Entry Point Verification

Ensure that your App struct correctly provides the environment object:

import SwiftUI

@main
struct LaserSwordTrainerApp: App {
    var multipeerManager = MultipeerManager()
    var connectionStatusViewModel = ControllerConnectionStatusViewModel(multipeerManager: multipeerManager)
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(connectionStatusViewModel)
        }
    }
}

This setup should ensure that the ControllerConnectionStatusViewModel is properly provided to the ContentView and that the onChange modifier correctly responds to changes in the hasBeenConnected property.

Troubleshoot `hasBeenConnected` SwiftUI created at Tue Jan 20 1970 21:18:22 GMT+0000 (Coordinated Universal Time)

Related Chats

SwiftUI View with Model 0.514

Fix Warning: Use StateObject 0.494

StateObject Error Resolution 0.482

Control Window Size: VisionOS 0.476

New chat 0.456

Quit App Button Swift 0.456

Add Function to SwiftUI. 0.449

Provide EnvironmentObject TrainerView 0.447

SwiftUI Drag Rotation Implementation 0.438