Reputation: 15217
My SwiftUI app renders a SCNScene
with the code below. The scene displays visible nodes that can be tapped to show an info.
When a tap to a node ends, hitTest
of the renderer
(saved in the viewModel
) is called. It returns the tapped node and sets isShowingVehicleInfo
so that the MainView
shows the VehicleInfoView
above the Board3DView
.
This works, but only the 1st time.
When the same node is tapped a 2nd time, hitTest
no longer finds the node.
If however, another mode is tapped, hitTest
finds the other node, but also only once.
I have a slightly modified code that behaves correctly, i.e. finds nodes always.
Instead of using ZStack
to display the node info, it uses a sheet
.
I prefer the ZStack
version, because it allows me to control the info size, whereas the sheet
version always presents a full size info.
I don't understand why the ZStack
version has this strange behavior to find a node only once.
Any help is welcome!
First code using ZStack
that finds a node only once:
struct MainView: View {
@State private var isShowingVehicleInfo = false
// …
var body: some View {
NavigationStack { // Required for a toolbar in iOS
VStack {
TitleAndToolbarView(isShowingVehicleTypeInfo: $isShowingVehicleTypeInfo,
isShowingVehicleInfo: $isShowingVehicleInfo,
viewModel: viewModel)
.padding(.top)
ZStack {
GeometryReader { geometry in
Board3DView(viewModel: viewModel)
.onChange(of: geometry.size) {
viewModel.setMainViewSize(geometry.size)
}
.onChange(of: viewModel.selectedVehicle, {
if viewModel.selectedVehicle != nil {
isShowingVehicleInfo = true
}
})
.edgesIgnoringSafeArea(.all)
} // geo reader
.padding(.top)
if isShowingVehicleInfo {
VehicleInfoView(isShowingVehicleInfo: $isShowingVehicleInfo,
isShowingCameraFullScale: $isShowingCameraFullScale,
viewModel: viewModel)
.background(.white)
}
}
}
}
}
}
struct Board3DView: View {
// …
var body: some View {
// Define a spacial tap gesture
let tapGesture: _EndedGesture<SpatialTapGesture> = SpatialTapGesture(count: 1)
.onEnded() { event in
// hit test
guard let renderer = viewModel.renderer else { return }
let hits = renderer.hitTest(event.location, options: [SCNHitTestOption.categoryBitMask: ~NodeType.cannotBeTapped.rawValue])
if let firstHitResult = hits.first {
// Here, isShowingVehicleInfo is set in the MainView
}
}
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
SceneView (
scene: viewModel.scene,
options: [], // .allowsCameraControl is disabled
delegate: viewModel
)
.simultaneousGesture( tapGesture )
// …
}
}
}
Second code using sheet
that finds the node always:
var body: some View {
NavigationStack { // Required for a toolbar in iOS
VStack {
TitleAndToolbarView(isShowingVehicleTypeInfo: $isShowingVehicleTypeInfo,
isShowingVehicleInfo: $isShowingVehicleInfo,
viewModel: viewModel)
.padding(.top)
GeometryReader { geometry in
Board3DView(viewModel: viewModel)
.onChange(of: geometry.size) {
viewModel.setMainViewSize(geometry.size)
}
.onChange(of: viewModel.selectedVehicle, {
if viewModel.selectedVehicle != nil {
isShowingVehicleInfo = true
}
})
.edgesIgnoringSafeArea(.all)
} // geo reader
.padding(.top)
.sheet(isPresented: $isShowingVehicleInfo, onDismiss: {
viewModel.deselectVehicle()
isShowingVehicleInfo = false
}) {
VehicleInfoView(isShowingVehicleInfo: $isShowingVehicleInfo,
isShowingCameraFullScale: $isShowingCameraFullScale,
viewModel: viewModel)
}
}
}
}
}
Upvotes: 0
Views: 19