ARKit: How can I add a UIView to ARKit Scene? - ios

I am working on an AR project using ARKit.
I want to add a UIView to ARKit Scene. When I tap on an object, I want to get information as a "pop-up" next to the object. This information is in a UIView.
Is it possible to add this UIView to ARKit Scene?
I set up this UIView as a scene and what can I do then?
Can I give it a node and then add it to the ARKit Scene? If so, how it works?
Or is there another way?
Thank you!
EDIT: Code of my SecondViewController
class InformationViewController: UIViewController {
#IBOutlet weak var secondView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.view = secondView
}
}
EDIT 2: Code in firstViewController
guard let secondViewController = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else {
print ("No secondController")
return
}
let plane = SCNPlane(width: CGFloat(0.1), height: CGFloat(0.1))
plane.firstMaterial?.diffuse.contents = secondViewController.view
let node = SCNNode(geometry: plane)
I only get a white screen of a plane, not the view.

The simplest (although undocumented) way to achieve that is to set a UIView backed by a view controller as diffuse contents of a material on a SCNPlane (or any other geometry really, but it works best with planes for obvious reasons).
let plane = SCNPlane()
plane.firstMaterial?.diffuse.contents = someViewController.view
let planeNode = SCNNode(geometry: plane)
You will have to persist the view controller somewhere otherwise it's going to be released and plane will not be visible. Using just a UIView without any UIViewController will throw an error.
The best thing about it is that it keeps all of the gestures and practically works just as a simple view. For example, if you use UITableViewController's view you will be able to scroll it right inside a scene.
I haven't tested it on iOS 10 and lower, but it's been working on iOS 11 so far. Works both in plain SceneKit scenes and with ARKit.

I cannot provide you code now but this is how to do it.
Create a SCNPlane.
Create your UIView with all elements you need.
Create image context from UIView.
Use this image as material for SCNPlane.
Or even easier make SKScene with label and add it as material for SCNPlane.
Example: https://stackoverflow.com/a/74380559/294884

To place text in a label in the world you draw it into an image and then attach that image to a SCNNode.
For example:
let text = "Hello, Stack Overflow."
let font = UIFont(name: "Arial", size: CGFloat(size))
let width = 128
let height = 128
let fontAttrs: [NSAttributedStringKey: Any] =
[NSAttributedStringKey.font: font as UIFont]
let stringSize = self.text.size(withAttributes: fontAttrs)
let rect = CGRect(x: CGFloat((width / 2.0) - (stringSize.width/2.0)),
y: CGFloat((height / 2.0) - (stringSize.height/2.0)),
width: CGFloat(stringSize.width),
height: CGFloat(stringSize.height))
let renderer = UIGraphicsImageRenderer(size: CGSize(width: CGFloat(width), height: CGFloat(height)))
let image = renderer.image { context in
let color = UIColor.blue.withAlphaComponent(CGFloat(0.5))
color.setFill()
context.fill(rect)
text.draw(with: rect, options: .usesLineFragmentOrigin, attributes: fontAttrs, context: nil)
}
let plane = SCNPlane(width: CGFloat(0.1), height: CGFloat(0.1))
plane.firstMaterial?.diffuse.contents = image
let node = SCNNode(geometry: plane)
EDIT:
I added these lines:
let color = UIColor.blue.withAlphaComponent(CGFloat(0.5))
color.setFill()
context.fill(rect)
This lets you set the background color and the opacity. There are other ways of doing this - which also let you draw complex shapes - but this is the easiest for basic color.
EDIT 2: Added reference to stringSize and rect

Related

Show image on SCNPlane in Scene kit & AR kit in swift iOS

I followed this raywenderlich tutorial to detect image using AR and Scene Kit. In this example they showed displaying video on top of detected image.
https://www.raywenderlich.com/6957-building-a-museum-app-with-arkit-2
I want to display image on top of detected image. Please help me how can I add childNode to SCNNode so that I can display image on top of it.
Here is the code which I tried it's not working showing a blank white screen.
DispatchQueue.main.async {
let size = imageAnchor.referenceImage.physicalSize
let imageView = UIImageView(image: UIImage(named: "BG.png")!)
let imgMaterial = SCNMaterial()
imgMaterial.diffuse.contents = imageView
let imgPlane = SCNPlane(width: size.width, height: size.height)
imgPlane.materials = [imgMaterial]
let imgNode = SCNNode(geometry: imgPlane)
imgNode.eulerAngles.x = -.pi / 2
node.addChildNode(imgNode)
node.opacity = 1
}
Please help to show image on top of detected image. Thanks in advance.
I'm able to show image on top of detected image but it takes lot of memory what is wrong in that code.
And same as image view I'm displaying gif play on top of detected image with the below code.
let size = imageAnchor.referenceImage.physicalSize
let imgNameFromDoc = person["actionToTake"] as! String
let documentsPathURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let imgpath = documentsPathURL?.appendingPathComponent("/Packages/\(self.selectedDatasetName)/3DScenes/\(imgNameFromDoc)")
let imageData = try! Data(contentsOf: URL(fileURLWithPath: imgpath!.path))
let imageURL = UIImage.gifImageWithData(imageData)
self.gifImgView = UIImageView(image: imageURL)
let imgMaterial = SCNMaterial()
imgMaterial.diffuse.contents = self.gifImgView
let imgPlane = SCNPlane(width: CGFloat(scaleActionSize(wid: Float(size.width), hei:Float(size.height))[0]), height: CGFloat(scaleActionSize(wid: Float(size.width), hei:Float(size.height))[1]))
imgPlane.materials = [imgMaterial]
let imgNode = SCNNode(geometry: imgPlane)
imgNode.eulerAngles.x = -.pi / 2
node.addChildNode(imgNode)
node.opacity = 1
With the help of image extension I'm showing GIF on image view on top of detected image.
https://github.com/kiritmodi2702/GIF-Swift/blob/master/GIF-Swift/iOSDevCenters%2BGIF.swift
How to load GIF image in Swift?
I dragged iOSDevCenters+GIF.swift class inside app and displayed gif.
When I use this gif image viewto display gif and image view to display image it takes more than 800 MB memory in Xcode running with iPad.
can somebody help me to find any memory leaks(tried with instruments not able to fix) or anything wrong in this code.
I guess you have image tracking working.
The next method is called when a new anchor is added. In this case when your image tracked is detected. So the parameter node is the image tracked and you can add a child to that image as in the tutorial. So you can try the next (use ARSCNViewDelegate):
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
DispatchQueue.main.async {
let imageView = UIImageView(image: UIImage())
let imgMaterial = SCNMaterial()
imgMaterial.diffuse.contents = imageView
let imgPlane = SCNPlane(width: 0.1, height: 0.1)
imgPlane.materials = [imgMaterial]
let imgNode = SCNNode(geometry: imgPlane)
imgNode.eulerAngles.x = -.pi / 2
node.addChildNode(imgNode)
node.opacity = 1
}
}
The imgNode is added to the node and the node is already added to the scene. So what happen if the node is not added to the scene? You have to add it by your own. So in case you are not using the renderer method, an example of it is the next one:
self.imgNode?.position = SCNVector3(0, 0, 0)
sceneView.scene.rootNode.addChildNode(self.imgNode)
Displaying UIKit views as material property contents is not supported.
Instead of a UIImageView you should directly use the UIImage or even better just a String or URL that points to the image on disk. This way the image won't be loaded twice in memory (in SceneKit and UIKit).

How to add button and label to the scene

I am new in Scenekit and ARKit, I wanted to add button and label inside UIView to the scenekit's scene. I am getting the world coordinates by the hit test to place the view but SCNNode don't have addSubView Method to add view.
I want to achieve following output :
My attempts to achieve this :
func addHotspot(result : SCNHitTestResult, parentNode : SCNNode? = nil) {
let view = UIView()
view.backgroundColor = .red
let material = SCNMaterial()
material.diffuse.contents = view
let plane = SCNPlane(width: 100, height: 100)
plane.materials = [material]
let node = SCNNode()
node.geometry = plane
node.position = result.worldCoordinates
parentNode?.addChildNode(node)
}
error :
Please suggest the way I can complete it.
To add a UIView object to a scene node, you can assign it to the node's geometry material diffuse content. It will be added to the node and will maintain its position as you move your device around:
DispatchQueue.main.async {
let view: UIView = getView() // this is your UIView instance that you want to add to the node
let material = SCNMaterial()
material.diffuse.contents = view
node.geometry.materials = [material]
}

How to manage SCNNode in ARKit as i require to switching from ARSCNView to Other view controller many time

I am working on ARKit app and my requirement for switching from ARSCNView to another Viewcontroller frequently and also need to manage SCNNode in ARSCNView.
Main Issue:
Sometimes placed SCNNode is moving very fast uncommonly and goes
out of AR screen.
Sometimes When I try to put a node on the
surface it does not judge the surface and floating node in Air only.
I want to put SCNNode on the surface with transform vertically.
Thanks.
# E.com Please check below sample code which I am using right now to add SCNNode with transform.
let planeGeometry = SCNPlane(width: CGFloat(ARRectWidth), height: CGFloat(ARRectHeight))
let material = SCNMaterial()
material.diffuse.contents = UIImage.init(named: "img")
let matirials : [SCNMaterial] = [material]
planeGeometry.materials = matirials
let rectNode = SCNNode(geometry: planeGeometry)
var transform = SCNMatrix4MakeRotation(-Float.pi / 2.0, 1.0, 0.0, 0.0)
transform = SCNMatrix4Rotate(transform, Float(orientation), 0, 1, 0)
rectNode.transform = transform
self.addChildNode(rectNode)
self.position = position

only detect in a section of camera preview layer, iOS, Swift

I am trying to get a detection zone in a live preview on my camera preview layer.
Is it possible for this, say there is a live feed and you have face detect on and as you look around it will only put a box around the face in a certain area for example a rectangle in the centre of the screen. all other faces in the preview that are outside of the rectangle don't get detected?
Im using Vision, iOS, Swift.
I figured this out by adding a guard before the CALayer adding
Before View did load
#IBOutlet weak var scanAreaImage: UIImageView!
var regionOfInterest: CGRect!
In View did load
scanAreaImage.frame is a image view that I put in via storyboard and this would represent the area I only wanted detection in,
let someRect: CGRect = scanAreaImage.frame
regionOfInterest = someRect
then in the vision text detection section.
func highlightLetters(box: VNRectangleObservation) {
let xCord = box.topLeft.x * (cameraPreviewlayer?.frame.size.width)!
let yCord = (1 - box.topLeft.y) * (cameraPreviewlayer?.frame.size.height)!
let width = (box.topRight.x - box.bottomLeft.x) * (cameraPreviewlayer?.frame.size.width)!
let height = (box.topLeft.y - box.bottomLeft.y) * (cameraPreviewlayer?.frame.size.height)!
// This is the section I Added for the rec of interest detection zone.
//////////////////////////////////////////////
let wordRect = CGRect(x: xCord, y: yCord, width: width, height: height)
guard regionOfInterest.contains(wordRect.origin) else { return } // only draw a box if the orgin of the word box is within the regionOfInterest
// regionOfInterest being the cgRect you created earlier
//////////////////////////////////////////////
let outline = CALayer()
outline.frame = CGRect(x: xCord, y: yCord, width: width, height: height)
outline.borderWidth = 1.0
if textColour == 1 {
outline.borderColor = UIColor.blue.cgColor
}else {
outline.borderColor = UIColor.clear.cgColor
}
cameraPreviewlayer?.addSublayer(outline)
this will only show outlines of the things inside the rectangle you created in storyboard. (Mine being the scanAreaImage)
I hope this helps someone.

How to set half-round UIImage in Swift like this screenshot

https://www.dropbox.com/s/wlizis5zybsvnfz/File%202017-04-04%2C%201%2052%2024%20PM.jpeg?dl=0
Hello all Swifters,
Could anyone tell me how to set this kind of UI? Is there any half rounded image that they have set?
Or there are two images. One with the mountains in the background and anohter image made half rounded in white background and placed in on top?
Please advise
Draw an ellipse shape using UIBezier path.
Draw a rectangle path exactly similar to imageView which holds your image.
Transform the ellipse path with CGAffineTransform so that it will be in the center of the rect path.
Translate rect path with CGAffineTransform by 0.5 to create intersection between ellipse and the rect.
Mask the image using CAShapeLayer.
Additional: As Rob Mayoff stated in comments you'll probably need to calculate the mask size in viewDidLayoutSubviews. Don't forget to play with it, test different cases (different screen sizes, orientations) and adjust the implementation based on your needs.
Try the following code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
guard let image = imageView.image else {
return
}
let size = image.size
imageView.clipsToBounds = true
imageView.image = image
let curveRadius = size.width * 0.010 // Adjust curve of the image view here
let invertedRadius = 1.0 / curveRadius
let rect = CGRect(x: 0,
y: -40,
width: imageView.bounds.width + size.width * 2 * invertedRadius,
height: imageView.bounds.height)
let ellipsePath = UIBezierPath(ovalIn: rect)
let transform = CGAffineTransform(translationX: -size.width * invertedRadius, y: 0)
ellipsePath.apply(transform)
let rectanglePath = UIBezierPath(rect: imageView.bounds)
rectanglePath.apply(CGAffineTransform(translationX: 0, y: -size.height * 0.5))
ellipsePath.append(rectanglePath)
let maskShapeLayer = CAShapeLayer()
maskShapeLayer.frame = imageView.bounds
maskShapeLayer.path = ellipsePath.cgPath
imageView.layer.mask = maskShapeLayer
}
}
Result:
You can find an answer here:
https://stackoverflow.com/a/34983655/5479510
But generally, I wouldn't recommend using a white image overlay as it may appear distorted or pixelated on different devices. Using a masking UIView would do just great.
Why you could not just create (draw) rounded transparent image and add UIImageView() with UIImage() at the top of the view with required height and below this view add other views. I this this is the easiest way. I would write comment but I cant.

Resources