How can I update the Observable Object from an ARView extension in SwiftUI? - swift

I am trying to Update the globalDataTransfer class from the ARView extension and reflect the changes on to ArView. Below is the globalDataTransfer function
class globalDataTransfer: ObservableObject{
#Published var val: String = "No Value"
required init (any : String){
self.val = any
}
func check() -> String{
return(self.val)
}
}
My ARViewContainer
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<ARViewContainer>) -> ARView {
let arView = ARView(frame: .zero,cameraMode: .ar,automaticallyConfigureSession: true)
arView.setupForBodyTracking()
arView.scene.addAnchor(bodySkeletonAnchor)
return(arView)
}
func updateUIView(_ uiView: ARView, context: Context) {
}
typealias UIViewType = ARView
}
My ARView extension is
extension ARView: ARSessionDelegate{
func setupForBodyTracking(){
let config = ARBodyTrackingConfiguration()
self.session.run(config)
self.session.delegate = self
}
public func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
for anchor in anchors{
if let bodyAnchor = anchor as? ARBodyAnchor{
if let skeleton = bodySkeleton{
skeleton.update(with: bodyAnchor)
var
}
else{
let skeleton = BodySkeleton(for: bodyAnchor)
bodySkeleton = skeleton
bodySkeletonAnchor.addChild(skeleton)
}
var Val:String = calcAngle(anchor: bodyAnchor) //Function that returns Joint Angles and lengths
}
}
}
}
Content View :
struct ArView: View {
#ObservedObject var forGlobalValue: globalDataTransfer = globalDataTransfer(any: "No Value")
var body: some View{
ZStack{
ARViewContainer()
VStack{
Text(forGlobalValue.val)
Text(" This is \(test(any: forGlobalValue))")
}
}
}
}
Can anyone let me know How do I pass this Observable object to the ARView or is there any way of getting the Val variable from ARView extension and update it regularly on my View Text.

Related

Can I control Reality Composer behaviors in RealityKit?

I would like to make a button using SwiftUI. When the button is pressed, the model will hide. I have already read the tutorial in this link (Creating a Trigger), but I don't know how to control it programmatically.
Here is my code:
struct VocabView : View {
#State private var arView = ARView(frame: .zero)
var body: some View {
ZStack{
ARViewContainer(arView: $arView)
.ignoresSafeArea()
VStack {
Button("hide") {
hide()
}
}
}
}
func hide() {
let demoScene = try! Experience1.loadDemo()
if arView.scene.anchors.count > 0 {
if arView.scene.anchors[0].isAnchored {
demoScene.notifications.hide.post()
}
}
}
}
struct ARViewContainer2: UIViewRepresentable {
#Binding var arView: ARView
func makeUIView(context: Context) -> ARView {
let demoScene = try! Experience1.loadDemo()
DispatchQueue.main.async {
arView.scene.anchors.append(demoScene)
}
return arView
}
}
Here is the configuration in Reality Composer:
You are loading your model twice – at first in makeUIView() method and secondly in hide() method. Try my version.
import SwiftUI
import RealityKit
struct ContentView : View {
#State private var arView = ARView(frame: .zero)
#State private var scene = try! Experience.loadBox()
var body: some View {
ZStack{
ARViewContainer(arView: $arView, scene: $scene)
.ignoresSafeArea()
VStack {
Spacer()
Button("Hide Model") { hideModel() }
}
}
}
private func hideModel() {
if arView.scene.anchors.count > 0 {
if arView.scene.anchors[0].isAnchored {
scene.notifications.notify.post()
}
}
}
}
struct ARViewContainer : UIViewRepresentable {
#Binding var arView: ARView
#Binding var scene: Experience.Box
func makeUIView(context: Context) -> ARView {
arView.scene.anchors.append(scene)
return arView
}
func updateUIView(_ view: ARView, context: Context) { }
}

How to open a SwiftUI view by tapping an entity in RealityKit?

I am using SwiftUI with RealityKit. As displayed in the code below, I have a plane entity that when tapped simply prints the name of the entity. What approach should I take toward navigating to a new view when I tap the entity? It would be preferable to navigate as with a navigation link in a normal view, but if that is not possible then perhaps a fullScreenCover?
ARViewContainer.swift:
class Coordinator: NSObject {
weak var view: ARView?
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
guard let view = self.view else { return }
let tapLocation = recognizer.location(in: view)
if let entity = view.entity(at: tapLocation) as? ModelEntity {
print(entity.name)
}
}
}
struct ARViewContainer: UIViewRepresentable {
typealias UIViewType = ARView
func makeUIView(context: Context) -> ARView{
let arView = ARView(frame: .zero, cameraMode: .ar, automaticallyConfigureSession: true)
context.coordinator.view = arView
arView.addGestureRecognizer(UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap)))
arView.scene.anchors.removeAll()
let anchor = AnchorEntity()
let plane = MeshResource.generatePlane(width: 1, height: 1)
var material = UnlitMaterial()
material.color = .init(tint: .white,
texture: .init(try! .load(named: "instagram")))
let planeEntity = ModelEntity(mesh: plane, materials: [material])
planeEntity.generateCollisionShapes(recursive: true)
planeEntity.name = "Plane Entity"
planeEntity.position.z -= 1.0
planeEntity.setParent(anchor)
arView.scene.addAnchor(anchor)
return arView
}
func updateUIView(_ uiView: ARView, context: Context){
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
}
ContentView.swift
struct ContentView: View {
#State var open = false
var body: some View {
NavigationView{
ZStack {
ARViewContainer()
.ignoresSafeArea(.all)
}
}
}
}
View I want to navigate to:
struct TestView : View {
var body : some View {
VStack{
Text("Test View")
}
}
}
Manage the state of the view in an observable object and modify it from your AR view.
struct ContentView: View {
#ObservedObject var settings = Settings.shared
var body: some View {
NavigationView {
ZStack {
ARViewContainer()
.ignoresSafeArea(.all)
NavigationLink("", isActive: $settings.shouldOpenDetailsView) {
TestView()
}
}
}
}
}
class Settings: ObservableObject {
static let shared = Settings()
#Published var shouldOpenDetailsView = false
}
class Coordinator: NSObject {
weak var view: ARView?
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
guard let view = self.view else { return }
let tapLocation = recognizer.location(in: view)
if let entity = view.entity(at: tapLocation) as? ModelEntity {
Settings.shared.shouldOpenDetailsView = true
}
}
}

Reality Composer – Failed to get the Notify trigger working

Simply trying to print a hello when I tap on an object in the Reality file I made in Reality Composer. Not able to set the link between Notify and in-app actions.
import SwiftUI
import RealityKit
struct ContentView: View {
var body: some View {
ZStack{
ARViewContainer()
Text("Level 1")
}
}
}
struct ARViewContainer : UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
let yellowEntity = try! ModelEntity.load(named: "Yellow")
let anchorEntity = AnchorEntity(plane: .horizontal)
anchorEntity.addChild(yellowEntity)
arView.scene.addAnchor(anchorEntity)
yellowEntity.actions.Yellowtapped.onAction = handleTap(_:)
func handleTap(_entity: Entity?){
guard let entity = entity else {return}
print("Hello")
}
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {
}
}
If you use .rcproject all works perfectly:
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
let scene = try! Experience.loadBox()
scene.actions.notifier.onAction = printer
let anchor = AnchorEntity(plane: .horizontal)
anchor.addChild(scene)
arView.scene.addAnchor(anchor)
return arView
}
func updateUIView(_ uiView: ARView, context: Context) { }
func printer(_ entity: Entity?) -> Void { print("Hello") }
}
P.S.
Do not forget to merge 2 actions together (in Reality Composer).

Swift: pass ARView to Coordinator

really stuck on this.
I'm trying to pass ARView from MakeUIView to makeCoordinator
I really need this to use ARView inside of #objc func handleTap
struct ARViewContainer: UIViewRepresentable{
func makeUIView(context: Context) -> ARView {
let myARView = ARView(frame: .zero)
//...config and things….
let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handleTap(_:)))
myARView.addGestureRecognizer(tapGesture)
return myARView
}
func makeCoordinator() -> Coordinator {
Coordinator("whatshouldiusehere", self.$focusObject, self.$focusName)
}
class Coordinator: NSObject {
private let view: ARView
private var object: Binding<Entity?>
private var objectname: Binding<String?>
init(_ view: ARView, _ obj: Binding<Entity?>, _ objname: Binding<String?>) {
self.objectname = objname
self.object = obj
self.view = view
super.init()
}
#objc func handleTap(_ sender: UIGestureRecognizer? = nil) {
guard let touchInView = sender?.location(in: view) else {
return
}
guard let hitEntity = view.entity(at: touchInView) else {return}
//doing something with object here, assigning to #Binding for example
}
}
}
I can't move myARView = ARView(frame: .zero) outside of makeUIView, cuz I'm using SwiftUI and it inits every time when variables changes.
But how I can pass it in any way?
Or any other option to access Binding with ARView same time.
A coordinator is available via context, so you can inject it via property, like
struct ARViewContainer: UIViewRepresentable{
func makeUIView(context: Context) -> ARView {
let myARView = ARView(frame: .zero)
//...config and things….
let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handleTap(_:)))
myARView.addGestureRecognizer(tapGesture)
context.coordinator.view = myARView // << inject here !!
return myARView
}
func makeCoordinator() -> Coordinator {
Coordinator(self.$focusObject, self.$focusName)
}
class Coordinator: NSObject {
var view: ARView? // << optional initially
private var object: Binding<Entity?>
private var objectname: Binding<String?>
init(_ obj: Binding<Entity?>, _ objname: Binding<String?>) {
self.objectname = objname
self.object = obj
super.init()
}
// ... other code update accordingly
}

Pass value from SwiftUI to ARKit dynamically

I'm currently engaging personal project using ARKit with Swift.
In this app, a skeleton is appearing by detected body and it tracks body's motion.
What I want to know is, how to change the position of skeleton by touching a slider dynamically.
The position of skeleton is defined in ARKit class ARDelegateHandler such as self.characterOffset = [1, 0, 0] which means 1m right from a detected body.
I want to change the characterOffset's x-axis value by slider in SwiftUI.(something like self.characterOffset = [x, 0, 0])
Here is my code.
ARViewContainer.swift
import SwiftUI
import RealityKit
import ARKit
import Combine
struct ARViewContainer: UIViewRepresentable {
let characterAnchor = AnchorEntity()
#Binding var offsetValue:Float
#ObservedObject var offsetInstance = Offset()
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
arView.session.delegate = context.coordinator
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {
let configuration = ARBodyTrackingConfiguration()
uiView.session.run(configuration)
uiView.scene.addAnchor(characterAnchor)
print(offsetValue)
}
func makeCoordinator() -> ARDelegateHandler {
ARDelegateHandler(self, anchor: characterAnchor)
}
class ARDelegateHandler: NSObject, ARSessionDelegate {
var arVC: ARViewContainer
let characterAnchor: AnchorEntity
var character: BodyTrackedEntity?
var characterOffset: SIMD3<Float>
init(_ control: ARViewContainer, anchor: AnchorEntity) {
self.arVC = control
self.characterAnchor = anchor
self.characterOffset = [1, 0, 0]
super.init()
setSkeleton()
}
func setSkeleton(){
var cancellable: AnyCancellable? = nil
cancellable = Entity.loadBodyTrackedAsync(named: "character/robot").sink(
receiveCompletion: { completion in
if case let .failure(error) = completion {
print("Error: Unable to load model: \(error.localizedDescription)")
}
cancellable?.cancel()
}, receiveValue: { (character: Entity) in
if let character = character as? BodyTrackedEntity {
character.scale = [1, 1, 1]
self.character = character
cancellable?.cancel()
} else {
print("Error: Unable to load model as BodyTrackedEntity")
}
})
}
func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
for anchor in anchors {
guard let bodyAnchor = anchor as? ARBodyAnchor else { continue }
let bodyPosition = simd_make_float3(bodyAnchor.transform.columns.3)
characterAnchor.position = bodyPosition + characterOffset
characterAnchor.orientation = Transform(matrix: bodyAnchor.transform).rotation
if let character = character, character.parent == nil {
characterAnchor.addChild(character)
}
}
}
}
}
ContentView.swift (User is expected to touch slider and offsetValue would be changed. SlideView has been already implemented)
import SwiftUI
import RealityKit
import ARKit
import Combine
struct ContentView : View {
#State var offsetValue:Float = 0.0
#ObservedObject var offsetInstance = Offset()
var body: some View {
VStack{
Button(action:{self.offsetInstance.setOffset(offset: 1.0)},
label:{Text("check")})
ZStack(alignment: .bottom) {
ARViewContainer(offsetValue: $offsetValue)
SlideView(offSetValue: $offsetValue)
}
}
}
}
Thank you very much for your help!