How to use SpatialTapGesture to pin a SwiftUI view to entity

My goal is to pin an attachment view precisely at the point where I tap on an entity using SpatialTapGesture. However, the current code doesn't pin the attachment view accurately to the tapped point. Instead, it often appears in space rather than on the entity itself. The issue might be due to an incorrect conversion of coordinates or values.

My code: struct ImmersiveView: View {

@State private var location: GlobeLocation?

var body: some View {
    RealityView { content, attachments in
        guard let rootEnity = try? await Entity(named: "Scene", in: realityKitContentBundle) else { return }
        
        content.add(rootEnity)
        
    }update: { content, attachments in

        if let earth = content.entities.first?.findEntity(named: "Earth"),let desView = attachments.entity(for: "1") {
            let pinTransform = computeTransform(for: location ?? GlobeLocation(latitude: 0, longitude: 0))
            
            
            earth.addChild(desView)

// desView.transform = desView.setPosition(pinTransform, relativeTo: earth) } } attachments: { Attachment(id: "1") { DescriptionView(location: location) } } .gesture(DragGesture().targetedToAnyEntity().onChanged({ value in value.entity.position = value.convert(value.location3D, from: .local, to: .scene) })) .gesture(SpatialTapGesture().targetedToAnyEntity().onEnded({ value in

    }))
}

func lookUpLocation(at value: CGPoint) -> GlobeLocation? {
    return GlobeLocation(latitude: value.x, longitude: value.y)
}


func computeTransform(for location: GlobeLocation) -> SIMD3<Float> {
    // Constants for Earth's radius. Adjust this to match the scale of your 3D model.
    let earthRadius: Float = 1.0

    // Convert latitude and longitude from degrees to radians
    let latitude = Float(location.latitude) * .pi / 180
    let longitude = Float(location.longitude) * .pi / 180

    // Calculate the position in Cartesian coordinates
    let x = earthRadius * cos(latitude) * cos(longitude)
    let y = earthRadius * sin(latitude)
    let z = earthRadius * cos(latitude) * sin(longitude)

    return position
}

}

struct GlobeLocation { var latitude: Double var longitude: Double }

Hi @sevens ,

Could you do something like this:

   .simultaneousGesture(
            SpatialTapGesture()
                .targetedToAnyEntity()
                .onEnded { value in
                    
                    let location3D = value.convert(value.location3D, from: .local, to: .scene)
                    
                    let entity = ModelEntity(mesh: .generateSphere(radius: 0.01), materials: [SimpleMaterial.init(color: .red, roughness: 0.1, isMetallic: true)])
                    value.entity.addChild(entity, preservingWorldTransform: false)
                    
                    entity.setPosition(location3D, relativeTo: nil)
                })

This code uses a spatial tap gesture to first get the location in the right coordinate space, then create an entity, and lastly add it to the tapped entity and set the position to be where you tapped.

Let me know if you need to use attachments, that's a bit more difficult to place.

Thanks for your solution, but I need it to look like this. This is Apple's demo: 14:04

my update code: import RealityKit import SwiftUI import RealityKitContent

struct ImmersiveView: View {

@State var favoritePoint = [PointOfInterest]()
@State var earthEntity: Entity = Entity()

    var body: some View {
        RealityView { content, attachments in
            // Load the root entity from your RealityKit content bundle
            guard let rootEntity = try? await Entity(named: "Scene", in: realityKitContentBundle) else { return }
            
            content.add(rootEntity)
            
            earthEntity = rootEntity.findEntity(named: "Earth") ?? Entity()
            
        } update: { content, attachments in
            // Update logic if needed
            for place in favoritePoint {
                if let placeEntity = attachments.entity(for: place.id) {
                    earthEntity.addChild(placeEntity)
                    placeEntity.setPosition(place.location, relativeTo: nil)
                }
            }
        } attachments: {
            // Define attachments using the Attachment type
            ForEach(favoritePoint) { place in
                Attachment(id: place.id) {
                    Text(place.name)
                        .padding()
                        .glassBackgroundEffect()
                        .tag(place.id)
                }
            }
        }
        .gesture(SpatialTapGesture().targetedToAnyEntity().onEnded({ value in
            let location = value.location3D
            let convertLocation = 1.1 * value.convert(location, from: .local, to: .scene)
            
            favoritePoint.append(PointOfInterest(name: "test1", location: convertLocation))
        }))
        
        .gesture(DragGesture().targetedToAnyEntity().onChanged({ value in
            value.entity.position = value.convert(value.location3D, from: .local, to: .scene)
        }))
    }

}

struct PointOfInterest: Identifiable { var name: String var id: UUID = UUID() var location: SIMD3<Float> }

with result:

i deleted it

1.1 *

Hey @sevens ,

It seems like you're missing the line placeEntity.look(at: .zero, from: place.location, relativeTo: placeEntity.parent) This is what makes the attachments not collide with the earth like yours are

@sevens ,

That's because you still have setPosition, unless you had removed it. You'll just want to use look, not both set and look. Try this code:


import SwiftUI
import RealityKit
import RealityKitContent


struct PinAttachment: View {
    @State private var placeAttachments: [PlaceAttachments] = []
    
    var body: some View {
        RealityView { content, attachments in
            guard let rootEntity = try? await Entity(named: "Scene", in: realityKitContentBundle) else { return }
            rootEntity.scale = [10, 10, 10]
            
            content.add(rootEntity)
            
        } update: { content, attachments in
            
            for place in placeAttachments {
                if let attachment = attachments.entity(for: place.id) {
                    content.add(attachment)
                    attachment.look(at: .zero, from: place.location, relativeTo: attachment.parent)
                }
            }
            
        } attachments: {
            ForEach(placeAttachments, id: \.id) { place in
                Attachment(id: place.id) {
                    Text(place.name)
                        .padding()
                        .glassBackgroundEffect()
                }
            }
        }
        
        .simultaneousGesture(
            SpatialTapGesture()
                .targetedToAnyEntity()
                .onEnded { value in
                    
                    let location3D = value.convert(value.location3D, from: .local, to: .scene)
                    let attachment = PlaceAttachments(name: "a place", location: location3D)
                    placeAttachments.append(attachment)
                })
    }
    
}

struct PlaceAttachments: Identifiable {
    let id: UUID = UUID()
    let name: String
    let location: SIMD3<Float>
}

Here's the link to the video if anyone is curious: https://developer.apple.com/videos/play/wwdc2023/10113

How to use SpatialTapGesture to pin a SwiftUI view to entity
 
 
Q