How can I replace a ModelEntity in RealityKit? - swift

I want to switch the ModelEntity from the fvBoatAnchor.fvBoatObject to the fvBridgeAnchor.fvBridgeObject with the click of a button, but I'm not sure how I do it, while also keeping the gestures and collisions as well as the image tracker after changing the entity.
Here is my code
import UIKit
import RealityKit
import ARKit
class fvVessel: UIViewController, ARSessionDelegate {
#IBOutlet var arView: ARView!
#IBOutlet weak var imageView: UIImageView!
let fvBoatAnchor = try! FV.loadFvBoatScene()
let fvBridgeAnchor = try! FV.loadFvBridgeScene()
var imageAnchorToEntity: [ARImageAnchor: AnchorEntity] = [:]
override func viewDidLoad() {
super.viewDidLoad()
let fvBoat = fvBoatAnchor.fvBoatObject as? Entity & HasCollision
arView.installGestures(for: fvBoat!)
fvBoatAnchor.generateCollisionShapes(recursive: true)
arView.scene.addAnchor(fvBoatAnchor)
arView.session.delegate = self
}
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
anchors.compactMap { $0 as? ARImageAnchor }.forEach {
let anchorEntity = AnchorEntity()
let modelEntity = fvBoatAnchor.fvBoatObject!
anchorEntity.addChild(modelEntity)
arView.scene.addAnchor(anchorEntity)
anchorEntity.transform.matrix = $0.transform
imageAnchorToEntity[$0] = anchorEntity
imageView.isHidden = true
}
}
// func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
// anchors.compactMap { $0 as? ARImageAnchor }.forEach {
// let anchorEntity = imageAnchorToEntity[$0]
// anchorEntity?.transform.matrix = $0.transform
// }
// }
func installGestures(on object:ModelEntity){
object.generateCollisionShapes(recursive: true)
arView.installGestures(.all, for: object)
}
func leaveScene() {
arView?.session.pause()
arView?.session.delegate = nil
arView?.scene.anchors.removeAll()
arView?.removeFromSuperview()
arView?.window?.resignKey()
arView = nil
}
#IBAction func leaveScene(_ sender: UIButton) {
leaveScene()
}
}

Related

Why am I unable to add anchor to arView scene after using removeAll()?

I'm trying to add and remove an anchor to my scene, but after removing it I'm unable to add it again. This might be because of the anchors added to the scene in the session function, but I'm not sure.
Do I need to run the session function again to add the anchorEntity to the scene again(not managed to do it due to some errors), or is there something else I'm missing...
Here is my code:
import UIKit
import RealityKit
import ARKit
class fvBoat: UIViewController, ARSessionDelegate {
#IBOutlet var arView: ARView!
let fvBoatAnchor = try! Vessel.loadFvBoatScene()
var imageAnchorToEntity: [ARImageAnchor: AnchorEntity] = [:]
override func viewDidLoad() {
super.viewDidLoad()
let fvBoat = fvBoatAnchor.fvBoatObject as? Entity & HasCollision
arView.installGestures(for: fvBoat!)
fvBoatAnchor.generateCollisionShapes(recursive: true)
// arView.scene.addAnchor(fvBoatAnchor)
arView.session.delegate = self
}
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
anchors.compactMap { $0 as? ARImageAnchor }.forEach {
let anchorEntity = AnchorEntity()
let modelEntity = fvBoatAnchor.fvBoatObject!
anchorEntity.addChild(modelEntity)
arView.scene.addAnchor(anchorEntity)
anchorEntity.transform.matrix = $0.transform
imageAnchorToEntity[$0] = anchorEntity
}
}
func installGestures(on object:ModelEntity){
object.generateCollisionShapes(recursive: true)
arView.installGestures(.all, for: object)
}
func leaveScene() {
arView?.session.pause()
arView?.session.delegate = nil
arView?.scene.anchors.removeAll()
arView?.removeFromSuperview()
arView?.window?.resignKey()
arView = nil
}
#IBAction func leaveScene(_ sender: Any) {
leaveScene()
}
#IBAction func addAnchor(_ sender: Any) {
arView.scene.addAnchor(fvBoatAnchor)
}
#IBAction func clearScene(_ sender: Any) {
arView.scene.anchors.removeAll()
}
}

SCNNode is not showing up

I'm new in Swift and ARKit. For some reason the SCNNode node I'm trying to display is not showing up. I'm working with SwiftUI. I defined in the next code block the function addNode that should render the node.
import Foundation
import ARKit
import SwiftUI
// MARK: - ARViewIndicator
struct ARViewIndicator: UIViewControllerRepresentable {
typealias UIViewControllerType = ARView
func makeUIViewController(context: Context) -> ARView {
return ARView()
}
func updateUIViewController(_ uiViewController:
ARViewIndicator.UIViewControllerType, context:
UIViewControllerRepresentableContext<ARViewIndicator>) { }
}
class ARView: UIViewController, ARSCNViewDelegate {
var arView: ARSCNView {
return self.view as! ARSCNView
}
override func loadView() {
self.view = ARSCNView(frame: .zero)
}
override func viewDidLoad() {
super.viewDidLoad()
arView.delegate = self
arView.scene = SCNScene()
}
// MARK: - Functions for standard AR view handling
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
arView.debugOptions = [.showFeaturePoints,
.showWorldOrigin]
arView.session.run(configuration)
arView.delegate = self
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
arView.session.pause()
}
func addNode(){
let node = SCNNode()
node.geometry = SCNBox(width: 0.1,
height: 0.1,
length: 0.1,
chamferRadius: 0)
node.geometry?.firstMaterial?.diffuse.contents = UIColor.blue
node.position = SCNVector3(0,0,0.3)
arView.scene.rootNode.addChildNode(node)
arView.delegate = self
print(123)
}
// MARK: - ARSCNViewDelegate
func sessionWasInterrupted(_ session: ARSession) {}
func sessionInterruptionEnded(_ session: ARSession) {}
func session(_ session: ARSession, didFailWithError error: Error)
{}
func session(_ session: ARSession, cameraDidChangeTrackingState
camera: ARCamera) {}
}
... and that function is invoked when clicking the button "HOME"
import SwiftUI
import ARKit
// MARK: - NavigationIndicator
struct NavigationIndicator: UIViewControllerRepresentable {
typealias UIViewControllerType = ARView
func makeUIViewController(context: Context) -> ARView {
return ARView()
}
func updateUIViewController(_ uiViewController:
NavigationIndicator.UIViewControllerType, context:
UIViewControllerRepresentableContext<NavigationIndicator>) { }
}
struct ContentView: View {
#State var page = "Home"
var body: some View {
VStack {
ZStack {
NavigationIndicator()
VStack {
Spacer()
HStack {
Button("Home") {
let ar = ARView();
ar.addNode()
}.padding()
.background(RoundedRectangle(cornerRadius: 10)
.foregroundColor(Color.white).opacity(0.7))
Spacer()
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Do you know why it's not showing up ?
Thanks in advance !
Use this approach for SceneKitView:
import SwiftUI
import ARKit
struct SceneKitView: UIViewRepresentable {
let arView = ARSCNView(frame: .zero)
#Binding var pressed: Bool
#Binding var node: SCNNode
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, ARSCNViewDelegate {
var control: SceneKitView
init(_ control: SceneKitView) {
self.control = control
}
func renderer(_ renderer: SCNSceneRenderer,
updateAtTime time: TimeInterval) {
if control.pressed {
self.control.node = self.addCube()
self.control.arView.scene.rootNode.addChildNode(control.node)
}
}
fileprivate func addCube() -> SCNNode {
control.node.geometry = SCNBox(width: 0.25,
height: 0.25,
length: 0.25,
chamferRadius: 0.01)
control.node.geometry?.firstMaterial?.diffuse.contents = UIColor.blue
control.node.geometry?.firstMaterial?.lightingModel = .phong
control.node.position = SCNVector3(0, 0,-2)
return control.node
}
}
func makeUIView(context: Context) -> ARSCNView {
arView.scene = SCNScene()
arView.delegate = context.coordinator
arView.autoenablesDefaultLighting = true
arView.debugOptions = .showFeaturePoints
// arView.allowsCameraControl = true
let config = ARWorldTrackingConfiguration()
arView.session.run(config)
return arView
}
func updateUIView(_ uiView: ARSCNView,
context: Context) { }
}
Then use this code for ContentView.
struct ContentView: View {
#State var pressed: Bool = false
#State var node = SCNNode()
var body: some View {
ZStack {
SceneKitView(pressed: $pressed, node: $node)
VStack {
Spacer()
HStack {
Button("Blue Cube") {
pressed.toggle()
}.padding()
.foregroundColor(.red)
Spacer()
}
}
}
}
}
P.S.
However, a strange issue occurs with ARSCNView in Simulator – after pressing a button a SCNBox appears only after tapping a screen with .allowsCameraControl = true.

uiactivity and uiwebview delegate

I have a uiactivity view in my project.
I want to start animating while the page is loading and stop and hide when the webview finally loads.
this is not working in my case.
I have tried to use the UIWebViewDelegate, but it is deprecated.
class WebviewViewController: UIViewController {
#IBOutlet var webView: UIWebView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
let disposeBag = DisposeBag()
var specURL:String?
override func viewDidLoad() {
super.viewDidLoad()
configureUIActivityView()
_ = loadWebview()
.subscribe(onSuccess: { url in
DispatchQueue.main.async {
self.webView.loadRequest(URLRequest(url: url))
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
}
})
}
func loadWebview() -> Single<URL> {
return Single<URL>.create { single in
guard let url = URL(string: self.specURL!.trimmingCharacters(in: .whitespacesAndNewlines)) else {
assertionFailure("no view available")
//single(.error(NetworkError.noImage))
return Disposables.create {}
}
single(.success(url))
return Disposables.create {}
}
}
}
It is better to use the WKWebView for achieving since as you mentioned that the UIWebViewDelegate is deprecated.
Here is a simple example for monitoring the loading status of the WKWebView using KVO.
import WebKit
class ViewController: UIViewController, WKUIDelegate {
private var webView: WKWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
webView.addObserver(self, forKeyPath: "loading", options: .new, context: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let myURL = URL(string: "http://www.apple.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath, let change = change else { return }
switch keyPath {
case "loading":
if let loading = change[.newKey] as? Bool {
print("Is webview loading: \(loading)")
}
default:
break
}
}
deinit {
webView.removeObserver(self, forKeyPath: "loading")
}
}

UITableView not displaying prototype cells

I am trying to make an app that connects to chromecast to play a video on TV, up till now I am still trying to display the video links using two view controllers, one contains a webview that makes the user gets the video page, the other is to display all video links inferred from the first view to make the user select which video to cast from the page. I am able to get the links but the problem is it doesn't want to be displayed in the table view cells. I have tried many methods but I noticed, for some reason the UITableViewDataSource methods are not being called at all. Here is the code:
ViewController.swift:
import UIKit
class ViewController: UIViewController, UIWebViewDelegate {
//MARK: Outlets
#IBOutlet weak var searchBar: UITextField!
#IBOutlet weak var webView: UIWebView!
#IBOutlet weak var cancelButton: UIButton!
#IBOutlet weak var searchBarTrailingConstraint: NSLayoutConstraint!
//MARK: Properties
static var videoURLs: [String] = []
//MARK: Methods
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
cancelButton.layer.cornerRadius = 5
cancelButton.isHidden = true
webView.delegate = self
}
func webViewDidFinishLoad(_ webView: UIWebView) {
var videoTag = ""
var embedTag = ""
let htmlCode = webView.stringByEvaluatingJavaScript(from: "document.documentElement.outerHTML")
let htmlTags = htmlCode!.components(separatedBy: "\n") as [String]
for tag in htmlTags{
var videoURL = ""
if tag.contains("<video") {
videoTag = tag.substring(from: tag.range(of: "<video")!.lowerBound)
videoTag = videoTag.substring(to: (videoTag.range(of: ">")?.upperBound)!)
if videoTag.contains("src"){
videoTag = tag.substring(from: tag.range(of: "src")!.upperBound)
for x in videoTag.characters{
if x == "\""{
continue
}else if x == "="{
continue
}else if x == ">"{
break
}else{
videoURL.append(x)
}
}
}
ViewController.videoURLs.append(videoURL)
}
if tag.contains("<embed") {
embedTag = tag.substring(from: tag.range(of: "<embed")!.lowerBound)
embedTag = embedTag.substring(to: (embedTag.range(of: ">")?.upperBound)!)
if embedTag.contains("src"){
embedTag = tag.substring(from: tag.range(of: "src")!.upperBound)
for x in embedTag.characters{
if x == "\""{
continue
}else if x == "="{
continue
}else if x == ">"{
break
}else{
videoURL.append(x)
}
}
}
ViewController.videoURLs.append(videoURL)
}
}
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "Done")))
}
//MARK: Actions
#IBAction func cancelPressed() {
cancelButton.isHidden = true
searchBarTrailingConstraint.constant = 0.0
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
searchBar.resignFirstResponder()
}
#IBAction func searchBarPressed() {
searchBarTrailingConstraint.constant = (cancelButton.frame.width + 8.0) * -1
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
cancelButton.isHidden = false
}
#IBAction func returnButtonPressed(_ sender: UITextField) {
cancelPressed()
if let url = URL(string: sender.text!){
if UIApplication.shared.canOpenURL(url){
let request = URLRequest(url: url)
webView.loadRequest(request)
}else{
let googleSearchURL = URL(string: "https://www.google.com/search?client=safari&q=\(url)&ie=UTF-8&oe=UTF-8")
let request = URLRequest(url: googleSearchURL!)
webView.loadRequest(request)
}
}else{
var searchString: [String] = []
var searchWord = ""
for x in (sender.text?.characters)!{
if x == " "{
searchString.append(searchWord)
searchWord = ""
}else{
searchWord.append(x)
}
}
//For appending the last word not followed by a space
if !(searchString.last == searchWord){
searchString.append(searchWord)
}
var googleSearchURL = "https://www.google.com/search?client=safari&ie=UTF-8&oe=UTF-8&q="
for element in searchString{
googleSearchURL.append(element)
if !(searchString.last == element){
googleSearchURL.append("+")
}
}
let request = URLRequest(url: URL(string:googleSearchURL)!)
webView.loadRequest(request)
}
}
#IBAction func backButtonPressed(_ sender: UIButton) {
if webView.canGoBack{
webView.goBack()
}
}
#IBAction func forwardButtonPressed(_ sender: UIButton) {
if webView.canGoForward {
webView.goForward()
}
}
}
MediaTableViewController:
import UIKit
import AVFoundation
class MediaTableViewController: UIViewController, UITableViewDataSource {
var videoURLs: [String] = []
var videoScreenshots: UIImage!
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(MediaTableViewController.replyToNotification), name: nil, object: nil)
self.tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videoURLs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("NOW!\n\n\n", indexPath.count)
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! MediaTableViewCell
cell.videoImage.image = videoScreenshot(url: videoURLs[indexPath.count])
cell.videoURL.text = videoURLs[indexPath.count]
return cell
}
#objc func replyToNotification(){
videoURLs = ViewController.videoURLs
ViewController.videoURLs = []
}
// MARK: - Table view data source
func videoScreenshot(url: String) -> UIImage? {
let asset = AVURLAsset(url: URL(string: url)!)
let generator = AVAssetImageGenerator(asset: asset)
generator.appliesPreferredTrackTransform = true
do {
let imageRef = try generator.copyCGImage(at: CMTime(value: asset.duration.value/2, timescale: asset.duration.timescale), actualTime: nil)
return UIImage(cgImage: imageRef)
}
catch let error as NSError
{
print("Image generation failed with error \(error)")
return nil
}
}
}
MediaTableViewCell.swift:
import UIKit
class MediaTableViewCell: UITableViewCell {
#IBOutlet weak var videoImage: UIImageView!
#IBOutlet weak var videoURL: UITextView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
Here is the Main.storyboard:
You do not call reloadData() thus the tableView is idle.
To fix this the following to MediaTableViewController:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableView.reloadData()
}
As I see you didn't implement tableView delegate
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(MediaTableViewController.replyToNotification), name: nil, object: nil)
self.tableView.delegate = self
self.tableView.dataSource = self
}
then add this method to your controller
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
hope this will help

iOS Swift delegates

I'm very new to Swift, and I'm having trouble using delegates. When the user taps on a table row in AdminAddCatTableViewController, I want to drop a pin on the map at the user's current location in AdminViewController, and I'm trying to do this using a delegate. Obviously there's something wrong with my code, as the pin does not get dropped.
In AdminAddCatTableViewController, I have
import UIKit
import Firebase
protocol AddCatDelegate: class {
func addPin(sender: AdminAddCatTableViewController)
}
class AdminAddCatTableViewController: UITableViewController {
weak var delegate:AddCatDelegate?
let admin = "secret-number"
let ref = Firebase(url: "firease_url")
#IBOutlet weak var snowballGPSLabel: UILabel!
#IBOutlet weak var smokeyGPSLabel: UILabel!
#IBOutlet weak var shadowGPSLabel: UILabel!
#IBOutlet weak var spotsGPSLabel: UILabel!
#IBOutlet weak var sunnyGPSLabel: UILabel!
var catRefArray: [AnyObject] = []
var coord:String = ""
let shareData = ShareData.sharedInstance
func updateCoord() {
if let bar = self.shareData.someString {
self.coord = bar
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "updateCoord", userInfo: nil, repeats: true)
var catNameArray: [AnyObject] = []
catNameArray.append("Snowball")
catNameArray.append("Smokey")
catNameArray.append("Shadow")
catNameArray.append("Spots")
catNameArray.append("Sunny")
for i in 0...4 {
catRefArray.append(self.ref.childByAppendingPath("admin").childByAppendingPath(self.admin).childByAppendingPath(catNameArray[i] as! String))
}
catRefArray[0].observeEventType(.Value, withBlock: { snapshot in
if let value:String = snapshot.value as? String {
self.snowballGPSLabel.text = value
}
}, withCancelBlock: { error in
print(error.description)
// same for the other rows
})
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (indexPath.row == 0) {
let ref0 = self.ref.childByAppendingPath("admin").childByAppendingPath(self.admin)
ref0.updateChildValues(["Snowball": self.coord])
delegate?.addPin(self)
}
// same for other rows
}
In AdminViewController, I have
import UIKit
import MapKit
import CoreLocation
class AdminViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBAction func logOutDidTouch(sender: AnyObject) {
performSegueWithIdentifier("adminToLogin", sender: self)
}
#IBOutlet weak var mapView: MKMapView!
var locationManager: CLLocationManager!
var previousLocation : CLLocation!
var latitude = 0.0;
var longitude = 0.0;
//Declare Class Variable
let shareData = ShareData.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
//On loading the screen the map kit view is shown and the current location is found and is being updated.
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate = self;
let status = CLLocationManager.authorizationStatus()
if status == .NotDetermined || status == .Denied || status == .AuthorizedWhenInUse {
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
}
locationManager.startUpdatingLocation()
locationManager.startUpdatingHeading()
mapView.delegate = self
mapView.showsUserLocation = true
mapView.mapType = MKMapType(rawValue: 0)!
mapView.userTrackingMode = MKUserTrackingMode(rawValue: 2)!
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
mapView.mapType = MKMapType(rawValue: 0)!
}
override func viewWillAppear(animated: Bool) {
//updates the location
locationManager.startUpdatingHeading()
locationManager.startUpdatingLocation()
}
override func viewWillDisappear(animated: Bool) {
locationManager.stopUpdatingHeading()
locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation) {
self.latitude = newLocation.coordinate.latitude
self.longitude = newLocation.coordinate.longitude
self.shareData.someString = "\(self.latitude)" + "," + "\(self.longitude)"
print(self.shareData.someString)
}
}
extension AdminViewController: AddCatDelegate {
func addPin(sender:AdminAddCatTableViewController) {
// drop a pin
self.mapView.delegate = self
let coordinate = mapView.userLocation.coordinate
let dropPin = MKPointAnnotation()
dropPin.coordinate = coordinate
dropPin.title = "Cat"
mapView.addAnnotation(dropPin)
}
}
Well you are never setting the delegate on your AdminAddCatTableViewController, so it is always nil and never called.
Why do you even have an extension of AdminViewController? Just remove the extension and make AdminViewController implement the delegate. To set the delegate implement something like this in your AdminViewController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue(segue, sender: sender)
if segue.identifier == "YourSegueIdentifier" {
if let vc = segue.destinationViewController as? AdminAddCatTableViewController {
vc.delegate = self
}
}
}